From 14cf90a2e4cd814bd09219a48e7915f75fd01478 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Tue, 1 Nov 2022 10:45:42 -0400 Subject: [PATCH 01/20] add mssql LIMIT/OFFSET syntax --- src/table.mjs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/table.mjs b/src/table.mjs index b2fe1072..047a08e6 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -254,15 +254,30 @@ export function makeQueryTemplate(operations, source) { appendSql(i ? `, ` : `\nORDER BY `, args); appendOrderBy(sort[i], args); } - if (slice.to !== null || slice.from !== null) { - appendSql( - `\nLIMIT ${slice.to !== null ? slice.to - (slice.from || 0) : 1e9}`, - args - ); - } - if (slice.from !== null) { - appendSql(` OFFSET ${slice.from}`, args); + if(source.dialect==='mssql'){ + if(!sort.length){ + appendSql(`\nORDER BY `, args); + appendOrderBy({column: select.columns[0], direction: 'ASC'}, args); + } + if (slice.to !== null || slice.from !== null) { + appendSql(`\nOFFSET ${slice.from ? slice.from : 0} ROWS`, args); + appendSql( + `\nFETCH NEXT ${slice.to !== null ? slice.to - (slice.from || 0) : 1e9} ROWS ONLY`, + args + ); + } + }else{ + if (slice.to !== null || slice.from !== null) { + appendSql( + `\nLIMIT ${slice.to !== null ? slice.to - (slice.from || 0) : 1e9}`, + args + ); + } + if (slice.from !== null) { + appendSql(` OFFSET ${slice.from}`, args); + } } + return args; } From 18a4442e4653e657fe6ba2afe9ae2014b2a5a84d Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Tue, 1 Nov 2022 10:53:59 -0400 Subject: [PATCH 02/20] tests --- test/table-test.mjs | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/table-test.mjs b/test/table-test.mjs index 462f0bea..ffbcc726 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -215,6 +215,52 @@ describe("makeQueryTemplate", () => { assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2,t.col3 FROM table1 t\nWHERE t.col1 >= ?\nAND t.col2 = ?\nORDER BY t.col1 ASC\nLIMIT 90 OFFSET 10"); assert.deepStrictEqual(params, ["val1", "val2"]); }); + + it("makeQueryTemplate select, slice with mssql syntax", () => { + const source = {name: "db", dialect: "mssql"}; + const operations = { + ...baseOperations, + select: { + columns: ["col1", "col2", "col3"] + }, + slice: {from: 10, to: 100}, + }; + + const [parts] = makeQueryTemplate(operations, source); + assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2,t.col3 FROM table1 t\nORDER BY t.col1 ASC\nOFFSET 10 ROWS\nFETCH NEXT 90 ROWS ONLY"); + }); + + it("makeQueryTemplate select, sort, slice, filter indexed with mssql syntax", () => { + const source = {name: "db", dialect: "mssql"}; + const operations = { + ...baseOperations, + select: { + columns: ["col1", "col2", "col3"] + }, + sort: [{column: "col2", direction: "desc"}], + slice: {from: 10, to: 100}, + filter: [ + { + type: "gte", + operands: [ + {type: "column", value: "col1"}, + {type: "resolved", value: "val1"} + ] + }, + { + type: "eq", + operands: [ + {type: "column", value: "col2"}, + {type: "resolved", value: "val2"} + ] + } + ] + }; + + const [parts, ...params] = makeQueryTemplate(operations, source); + assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2,t.col3 FROM table1 t\nWHERE t.col1 >= ?\nAND t.col2 = ?\nORDER BY t.col2 DESC\nOFFSET 10 ROWS\nFETCH NEXT 90 ROWS ONLY"); + assert.deepStrictEqual(params, ["val1", "val2"]); + }); }); describe("__table", () => { From a1136c541fabe30772a56bbe12e98ad82552f3ea Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Tue, 1 Nov 2022 11:05:42 -0400 Subject: [PATCH 03/20] lint --- test/table-test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/table-test.mjs b/test/table-test.mjs index ffbcc726..427fecd3 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -223,7 +223,7 @@ describe("makeQueryTemplate", () => { select: { columns: ["col1", "col2", "col3"] }, - slice: {from: 10, to: 100}, + slice: {from: 10, to: 100} }; const [parts] = makeQueryTemplate(operations, source); From 2aa598cf4c4176dadae2c236d8f80ffc5ed73391 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Tue, 1 Nov 2022 11:09:45 -0400 Subject: [PATCH 04/20] correct ORDER BY injection position --- src/table.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/table.mjs b/src/table.mjs index 047a08e6..a8602a93 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -255,11 +255,11 @@ export function makeQueryTemplate(operations, source) { appendOrderBy(sort[i], args); } if(source.dialect==='mssql'){ - if(!sort.length){ - appendSql(`\nORDER BY `, args); - appendOrderBy({column: select.columns[0], direction: 'ASC'}, args); - } if (slice.to !== null || slice.from !== null) { + if(!sort.length){ + appendSql(`\nORDER BY `, args); + appendOrderBy({column: select.columns[0], direction: 'ASC'}, args); + } appendSql(`\nOFFSET ${slice.from ? slice.from : 0} ROWS`, args); appendSql( `\nFETCH NEXT ${slice.to !== null ? slice.to - (slice.from || 0) : 1e9} ROWS ONLY`, From 07328c0970a94c4374d2495c1bb73c195a55f6a5 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Tue, 1 Nov 2022 11:15:15 -0400 Subject: [PATCH 05/20] more concise --- src/table.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/table.mjs b/src/table.mjs index a8602a93..d97e915a 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -260,7 +260,7 @@ export function makeQueryTemplate(operations, source) { appendSql(`\nORDER BY `, args); appendOrderBy({column: select.columns[0], direction: 'ASC'}, args); } - appendSql(`\nOFFSET ${slice.from ? slice.from : 0} ROWS`, args); + appendSql(`\nOFFSET ${slice.from || 0} ROWS`, args); appendSql( `\nFETCH NEXT ${slice.to !== null ? slice.to - (slice.from || 0) : 1e9} ROWS ONLY`, args From 8f3e25d970223598caade58309923eac1c9ded9e Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Tue, 1 Nov 2022 11:16:37 -0400 Subject: [PATCH 06/20] test for no slice from --- test/table-test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/table-test.mjs b/test/table-test.mjs index 427fecd3..e191210b 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -223,11 +223,11 @@ describe("makeQueryTemplate", () => { select: { columns: ["col1", "col2", "col3"] }, - slice: {from: 10, to: 100} + slice: {to: 100} }; const [parts] = makeQueryTemplate(operations, source); - assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2,t.col3 FROM table1 t\nORDER BY t.col1 ASC\nOFFSET 10 ROWS\nFETCH NEXT 90 ROWS ONLY"); + assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2,t.col3 FROM table1 t\nORDER BY t.col1 ASC\nOFFSET 0 ROWS\nFETCH NEXT 100 ROWS ONLY"); }); it("makeQueryTemplate select, sort, slice, filter indexed with mssql syntax", () => { From e5dfbf89b634f0e779b984bd4dc1d0b6535748e0 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Tue, 1 Nov 2022 11:21:16 -0400 Subject: [PATCH 07/20] lint 2.0 --- src/table.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/table.mjs b/src/table.mjs index d97e915a..b7b9a5f6 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -254,7 +254,7 @@ export function makeQueryTemplate(operations, source) { appendSql(i ? `, ` : `\nORDER BY `, args); appendOrderBy(sort[i], args); } - if(source.dialect==='mssql'){ + if(source.dialect === 'mssql'){ if (slice.to !== null || slice.from !== null) { if(!sort.length){ appendSql(`\nORDER BY `, args); From e7a5806643bf6fa051c40fc3c727c40a6de0c581 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Tue, 1 Nov 2022 17:00:32 -0400 Subject: [PATCH 08/20] cleanup whitespace --- src/table.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/table.mjs b/src/table.mjs index b7b9a5f6..546913c7 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -277,7 +277,6 @@ export function makeQueryTemplate(operations, source) { appendSql(` OFFSET ${slice.from}`, args); } } - return args; } From 1b91cdce9f8427d8782a02c0b50e2e6b649beffe Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Wed, 2 Nov 2022 09:47:12 -0400 Subject: [PATCH 09/20] escaping sort, filter and order by --- src/table.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/table.mjs b/src/table.mjs index 546913c7..21c13f88 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -248,17 +248,24 @@ export function makeQueryTemplate(operations, source) { ]; for (let i = 0; i < filter.length; ++i) { appendSql(i ? `\nAND ` : `\nWHERE `, args); + filter[i].operands.forEach(op => { + if(op.type === 'column') + op.value = escaper(op.value); + }); appendWhereEntry(filter[i], args); } for (let i = 0; i < sort.length; ++i) { appendSql(i ? `, ` : `\nORDER BY `, args); + sort[i].column = escaper(sort[i].column); appendOrderBy(sort[i], args); } if(source.dialect === 'mssql'){ if (slice.to !== null || slice.from !== null) { if(!sort.length){ + if (columns[0] === "*") + throw new Error("at least one column must be explicitly specified. Received '*'."); appendSql(`\nORDER BY `, args); - appendOrderBy({column: select.columns[0], direction: 'ASC'}, args); + appendOrderBy({column: escaper(select.columns[0]), direction: 'ASC'}, args); } appendSql(`\nOFFSET ${slice.from || 0} ROWS`, args); appendSql( From cf1e5045710f22860d0ab443a4bdc58160d19989 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Wed, 2 Nov 2022 09:54:12 -0400 Subject: [PATCH 10/20] throw if selected column is * with mssql --- test/table-test.mjs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/table-test.mjs b/test/table-test.mjs index e191210b..42a0e69b 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -261,6 +261,38 @@ describe("makeQueryTemplate", () => { assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2,t.col3 FROM table1 t\nWHERE t.col1 >= ?\nAND t.col2 = ?\nORDER BY t.col2 DESC\nOFFSET 10 ROWS\nFETCH NEXT 90 ROWS ONLY"); assert.deepStrictEqual(params, ["val1", "val2"]); }); + + it("makeQueryTemplate throw if no columns are explicitly specified for mssql dialect", () => { + const source = {name: "db", dialect: "mssql"}; + const operations = { + ...baseOperations, + select: { + columns: [] + }, + sort: [{column: "col2", direction: "desc"}], + slice: {from: 10, to: 100}, + filter: [ + { + type: "gte", + operands: [ + {type: "column", value: "col1"}, + {type: "resolved", value: "val1"} + ] + }, + { + type: "eq", + operands: [ + {type: "column", value: "col2"}, + {type: "resolved", value: "val2"} + ] + } + ] + }; + + assert.throws(() =>{ + makeQueryTemplate(operations, source); + }, Error); + }); }); describe("__table", () => { From a14b272a8d3ea0d70e15d22fb3199b5c20efd6ae Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Wed, 2 Nov 2022 10:22:35 -0400 Subject: [PATCH 11/20] test for escaping --- test/table-test.mjs | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/test/table-test.mjs b/test/table-test.mjs index 42a0e69b..fe5bc810 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -106,6 +106,26 @@ describe("makeQueryTemplate", () => { assert.deepStrictEqual(params, ["val1"]); }); + it("makeQueryTemplate filter and escape filters column", () => { + const source = {name: "db", dialect: "postgres", escape: (i) => `_${i}_`}; + const operations = { + ...baseOperations, + filter: [ + { + type: "eq", + operands: [ + {type: "column", value: "col2"}, + {type: "resolved", value: "val1"} + ] + } + ] + }; + + const [parts, ...params] = makeQueryTemplate(operations, source); + assert.deepStrictEqual(parts.join("?"), "SELECT t._col1_,t._col2_ FROM table1 t\nWHERE t._col2_ = ?"); + assert.deepStrictEqual(params, ["val1"]); + }); + it("makeQueryTemplate filter list", () => { const source = {name: "db", dialect: "postgres"}; const operations = { @@ -164,6 +184,21 @@ describe("makeQueryTemplate", () => { assert.deepStrictEqual(params, []); }); + it("makeQueryTemplate sort and escape sort column", () => { + const source = {name: "db", dialect: "mysql", escape: (i) => `_${i}_`}; + const operations = { + ...baseOperations, + sort: [ + {column: "col1", direction: "asc"}, + {column: "col2", direction: "desc"} + ] + }; + + const [parts, ...params] = makeQueryTemplate(operations, source); + assert.deepStrictEqual(parts.join("?"), "SELECT t._col1_,t._col2_ FROM table1 t\nORDER BY t._col1_ ASC, t._col2_ DESC"); + assert.deepStrictEqual(params, []); + }); + it("makeQueryTemplate slice", () => { const source = {name: "db", dialect: "mysql"}; const operations = {...baseOperations}; @@ -216,8 +251,8 @@ describe("makeQueryTemplate", () => { assert.deepStrictEqual(params, ["val1", "val2"]); }); - it("makeQueryTemplate select, slice with mssql syntax", () => { - const source = {name: "db", dialect: "mssql"}; + it("makeQueryTemplate select, slice and escape column name with mssql syntax", () => { + const source = {name: "db", dialect: "mssql", escape: (i) => `_${i}_`}; const operations = { ...baseOperations, select: { @@ -227,7 +262,7 @@ describe("makeQueryTemplate", () => { }; const [parts] = makeQueryTemplate(operations, source); - assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2,t.col3 FROM table1 t\nORDER BY t.col1 ASC\nOFFSET 0 ROWS\nFETCH NEXT 100 ROWS ONLY"); + assert.deepStrictEqual(parts.join("?"), "SELECT t._col1_,t._col2_,t._col3_ FROM table1 t\nORDER BY t._col1_ ASC\nOFFSET 0 ROWS\nFETCH NEXT 100 ROWS ONLY"); }); it("makeQueryTemplate select, sort, slice, filter indexed with mssql syntax", () => { From 333beef2d6e03c981927ee151ba4883befe0ccbf Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Wed, 2 Nov 2022 10:30:06 -0400 Subject: [PATCH 12/20] prettier table.mjs --- src/table.mjs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/table.mjs b/src/table.mjs index 21c13f88..f10c65cb 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -259,25 +259,32 @@ export function makeQueryTemplate(operations, source) { sort[i].column = escaper(sort[i].column); appendOrderBy(sort[i], args); } - if(source.dialect === 'mssql'){ + if (source.dialect === "mssql") { if (slice.to !== null || slice.from !== null) { - if(!sort.length){ + if (!sort.length) { if (columns[0] === "*") - throw new Error("at least one column must be explicitly specified. Received '*'."); + throw new Error( + "at least one column must be explicitly specified. Received '*'." + ); appendSql(`\nORDER BY `, args); - appendOrderBy({column: escaper(select.columns[0]), direction: 'ASC'}, args); + appendOrderBy( + {column: escaper(select.columns[0]), direction: "ASC"}, + args + ); } appendSql(`\nOFFSET ${slice.from || 0} ROWS`, args); appendSql( - `\nFETCH NEXT ${slice.to !== null ? slice.to - (slice.from || 0) : 1e9} ROWS ONLY`, - args + `\nFETCH NEXT ${ + slice.to !== null ? slice.to - (slice.from || 0) : 1e9 + } ROWS ONLY`, + args ); } - }else{ + } else { if (slice.to !== null || slice.from !== null) { appendSql( - `\nLIMIT ${slice.to !== null ? slice.to - (slice.from || 0) : 1e9}`, - args + `\nLIMIT ${slice.to !== null ? slice.to - (slice.from || 0) : 1e9}`, + args ); } if (slice.from !== null) { From 71c0bfcb505e55b9d13381e8abe25c19e4a9e6f8 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Wed, 2 Nov 2022 11:13:51 -0400 Subject: [PATCH 13/20] prettier table-test.mjs --- test/table-test.mjs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/test/table-test.mjs b/test/table-test.mjs index fe5bc810..7fbdab12 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -122,7 +122,10 @@ describe("makeQueryTemplate", () => { }; const [parts, ...params] = makeQueryTemplate(operations, source); - assert.deepStrictEqual(parts.join("?"), "SELECT t._col1_,t._col2_ FROM table1 t\nWHERE t._col2_ = ?"); + assert.deepStrictEqual( + parts.join("?"), + "SELECT t._col1_,t._col2_ FROM table1 t\nWHERE t._col2_ = ?" + ); assert.deepStrictEqual(params, ["val1"]); }); @@ -195,7 +198,10 @@ describe("makeQueryTemplate", () => { }; const [parts, ...params] = makeQueryTemplate(operations, source); - assert.deepStrictEqual(parts.join("?"), "SELECT t._col1_,t._col2_ FROM table1 t\nORDER BY t._col1_ ASC, t._col2_ DESC"); + assert.deepStrictEqual( + parts.join("?"), + "SELECT t._col1_,t._col2_ FROM table1 t\nORDER BY t._col1_ ASC, t._col2_ DESC" + ); assert.deepStrictEqual(params, []); }); @@ -252,7 +258,7 @@ describe("makeQueryTemplate", () => { }); it("makeQueryTemplate select, slice and escape column name with mssql syntax", () => { - const source = {name: "db", dialect: "mssql", escape: (i) => `_${i}_`}; + const source = {name: "db", dialect: "mssql", escape: (i) => `_${i}_`}; const operations = { ...baseOperations, select: { @@ -262,7 +268,10 @@ describe("makeQueryTemplate", () => { }; const [parts] = makeQueryTemplate(operations, source); - assert.deepStrictEqual(parts.join("?"), "SELECT t._col1_,t._col2_,t._col3_ FROM table1 t\nORDER BY t._col1_ ASC\nOFFSET 0 ROWS\nFETCH NEXT 100 ROWS ONLY"); + assert.deepStrictEqual( + parts.join("?"), + "SELECT t._col1_,t._col2_,t._col3_ FROM table1 t\nORDER BY t._col1_ ASC\nOFFSET 0 ROWS\nFETCH NEXT 100 ROWS ONLY" + ); }); it("makeQueryTemplate select, sort, slice, filter indexed with mssql syntax", () => { @@ -323,8 +332,8 @@ describe("makeQueryTemplate", () => { } ] }; - - assert.throws(() =>{ + + assert.throws(() => { makeQueryTemplate(operations, source); }, Error); }); From d889a6fa57344bbdad3b1545dc6716f51fd0621b Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Fri, 4 Nov 2022 15:11:53 -0400 Subject: [PATCH 14/20] checking for operands in filter and throw if missing --- src/table.mjs | 6 +++--- test/table-test.mjs | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/table.mjs b/src/table.mjs index f10c65cb..cb58fe9a 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -248,9 +248,9 @@ export function makeQueryTemplate(operations, source) { ]; for (let i = 0; i < filter.length; ++i) { appendSql(i ? `\nAND ` : `\nWHERE `, args); - filter[i].operands.forEach(op => { - if(op.type === 'column') - op.value = escaper(op.value); + if (!filter[i].operands) throw new Error("missing operands"); + filter[i].operands.forEach((op) => { + if (op.type === "column") op.value = escaper(op.value); }); appendWhereEntry(filter[i], args); } diff --git a/test/table-test.mjs b/test/table-test.mjs index 7fbdab12..d3d23403 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -158,6 +158,29 @@ describe("makeQueryTemplate", () => { assert.deepStrictEqual(params, ["val1", "val2", "val3", "val4"]); }); + it("makeQueryTemplate throw if filter is missing operands", () => { + const source = {name: "db", dialect: "postgres"}; + const operations = { + ...baseOperations, + filter: [ + { + type: "in" + }, + { + type: "nin", + operands: [ + {type: "column", value: "col1"}, + {type: "resolved", value: "val4"} + ] + } + ] + }; + + assert.throws(() => { + makeQueryTemplate(operations, source); + }, Error); + }); + it("makeQueryTemplate select", () => { const source = {name: "db", dialect: "mysql"}; const operations = { From 267af6c23e664e8e61e0799ba1f25dcbb5a5da33 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Fri, 4 Nov 2022 16:58:43 -0400 Subject: [PATCH 15/20] do not mutate escape when needed --- src/table.mjs | 15 +++++++-------- test/table-test.mjs | 43 ++++++++++++++++++++++--------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/table.mjs b/src/table.mjs index cb58fe9a..1bc9d227 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -246,17 +246,13 @@ export function makeQueryTemplate(operations, source) { const args = [ [`SELECT ${columns} FROM ${formatTable(from.table, escaper)} t`] ]; + args.escaper = escaper; for (let i = 0; i < filter.length; ++i) { appendSql(i ? `\nAND ` : `\nWHERE `, args); - if (!filter[i].operands) throw new Error("missing operands"); - filter[i].operands.forEach((op) => { - if (op.type === "column") op.value = escaper(op.value); - }); appendWhereEntry(filter[i], args); } for (let i = 0; i < sort.length; ++i) { appendSql(i ? `, ` : `\nORDER BY `, args); - sort[i].column = escaper(sort[i].column); appendOrderBy(sort[i], args); } if (source.dialect === "mssql") { @@ -268,7 +264,7 @@ export function makeQueryTemplate(operations, source) { ); appendSql(`\nORDER BY `, args); appendOrderBy( - {column: escaper(select.columns[0]), direction: "ASC"}, + {column: select.columns[0], direction: "ASC"}, args ); } @@ -291,6 +287,7 @@ export function makeQueryTemplate(operations, source) { appendSql(` OFFSET ${slice.from}`, args); } } + delete args.escaper; return args; } @@ -311,7 +308,8 @@ function appendSql(sql, args) { } function appendOrderBy({column, direction}, args) { - appendSql(`t.${column} ${direction.toUpperCase()}`, args); + const escaper = args.escaper; + appendSql(`t.${escaper(column)} ${direction.toUpperCase()}`, args); } function appendWhereEntry({type, operands}, args) { @@ -396,7 +394,8 @@ function appendWhereEntry({type, operands}, args) { function appendOperand(o, args) { if (o.type === "column") { - appendSql(`t.${o.value}`, args); + const escaper = args.escaper; + appendSql(`t.${escaper(o.value)}`, args); } else { args.push(o.value); args[0].push(""); diff --git a/test/table-test.mjs b/test/table-test.mjs index d3d23403..0f3b2716 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -129,42 +129,43 @@ describe("makeQueryTemplate", () => { assert.deepStrictEqual(params, ["val1"]); }); - it("makeQueryTemplate filter list", () => { - const source = {name: "db", dialect: "postgres"}; + it("makeQueryTemplate filter and escape filters column only once", () => { + const source = {name: "db", dialect: "postgres", escape: (i) => `_${i}_`}; const operations = { ...baseOperations, filter: [ { - type: "in", - operands: [ - {type: "column", value: "col1"}, - {type: "resolved", value: "val1"}, - {type: "resolved", value: "val2"}, - {type: "resolved", value: "val3"} - ] - }, - { - type: "nin", + type: "eq", operands: [ - {type: "column", value: "col1"}, - {type: "resolved", value: "val4"} + {type: "column", value: "col2"}, + {type: "resolved", value: "val1"} ] } ] }; + makeQueryTemplate(operations, source); const [parts, ...params] = makeQueryTemplate(operations, source); - assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2 FROM table1 t\nWHERE t.col1 IN (?,?,?)\nAND t.col1 NOT IN (?)"); - assert.deepStrictEqual(params, ["val1", "val2", "val3", "val4"]); + assert.deepStrictEqual( + parts.join("?"), + "SELECT t._col1_,t._col2_ FROM table1 t\nWHERE t._col2_ = ?" + ); + assert.deepStrictEqual(params, ["val1"]); }); - it("makeQueryTemplate throw if filter is missing operands", () => { + it("makeQueryTemplate filter list", () => { const source = {name: "db", dialect: "postgres"}; const operations = { ...baseOperations, filter: [ { - type: "in" + type: "in", + operands: [ + {type: "column", value: "col1"}, + {type: "resolved", value: "val1"}, + {type: "resolved", value: "val2"}, + {type: "resolved", value: "val3"} + ] }, { type: "nin", @@ -176,9 +177,9 @@ describe("makeQueryTemplate", () => { ] }; - assert.throws(() => { - makeQueryTemplate(operations, source); - }, Error); + const [parts, ...params] = makeQueryTemplate(operations, source); + assert.deepStrictEqual(parts.join("?"), "SELECT t.col1,t.col2 FROM table1 t\nWHERE t.col1 IN (?,?,?)\nAND t.col1 NOT IN (?)"); + assert.deepStrictEqual(params, ["val1", "val2", "val3", "val4"]); }); it("makeQueryTemplate select", () => { From f4a8b6de4a76d078063c8f4a0fe352aaf3781d3c Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 4 Nov 2022 14:12:37 -0700 Subject: [PATCH 16/20] pass escaper as an argument (#314) --- src/table.mjs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/table.mjs b/src/table.mjs index 1bc9d227..219a5f11 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -246,14 +246,13 @@ export function makeQueryTemplate(operations, source) { const args = [ [`SELECT ${columns} FROM ${formatTable(from.table, escaper)} t`] ]; - args.escaper = escaper; for (let i = 0; i < filter.length; ++i) { appendSql(i ? `\nAND ` : `\nWHERE `, args); - appendWhereEntry(filter[i], args); + appendWhereEntry(filter[i], args, escaper); } for (let i = 0; i < sort.length; ++i) { appendSql(i ? `, ` : `\nORDER BY `, args); - appendOrderBy(sort[i], args); + appendOrderBy(sort[i], args, escaper); } if (source.dialect === "mssql") { if (slice.to !== null || slice.from !== null) { @@ -265,7 +264,8 @@ export function makeQueryTemplate(operations, source) { appendSql(`\nORDER BY `, args); appendOrderBy( {column: select.columns[0], direction: "ASC"}, - args + args, + escaper ); } appendSql(`\nOFFSET ${slice.from || 0} ROWS`, args); @@ -287,7 +287,6 @@ export function makeQueryTemplate(operations, source) { appendSql(` OFFSET ${slice.from}`, args); } } - delete args.escaper; return args; } @@ -307,17 +306,16 @@ function appendSql(sql, args) { strings[strings.length - 1] += sql; } -function appendOrderBy({column, direction}, args) { - const escaper = args.escaper; +function appendOrderBy({column, direction}, args, escaper) { appendSql(`t.${escaper(column)} ${direction.toUpperCase()}`, args); } -function appendWhereEntry({type, operands}, args) { +function appendWhereEntry({type, operands}, args, escaper) { if (operands.length < 1) throw new Error("Invalid operand length"); // Unary operations if (operands.length === 1) { - appendOperand(operands[0], args); + appendOperand(operands[0], args, escaper); switch (type) { case "n": appendSql(` IS NULL`, args); @@ -336,7 +334,7 @@ function appendWhereEntry({type, operands}, args) { // Fallthrough to next parent block. } else if (["c", "nc"].includes(type)) { // TODO: Case (in)sensitive? - appendOperand(operands[0], args); + appendOperand(operands[0], args, escaper); switch (type) { case "c": appendSql(` LIKE `, args); @@ -345,10 +343,10 @@ function appendWhereEntry({type, operands}, args) { appendSql(` NOT LIKE `, args); break; } - appendOperand(likeOperand(operands[1]), args); + appendOperand(likeOperand(operands[1]), args, escaper); return; } else { - appendOperand(operands[0], args); + appendOperand(operands[0], args, escaper); switch (type) { case "eq": appendSql(` = `, args); @@ -371,13 +369,13 @@ function appendWhereEntry({type, operands}, args) { default: throw new Error("Invalid filter operation"); } - appendOperand(operands[1], args); + appendOperand(operands[1], args, escaper); return; } } // List operations - appendOperand(operands[0], args); + appendOperand(operands[0], args, escaper); switch (type) { case "in": appendSql(` IN (`, args); @@ -392,9 +390,8 @@ function appendWhereEntry({type, operands}, args) { appendSql(")", args); } -function appendOperand(o, args) { +function appendOperand(o, args, escaper) { if (o.type === "column") { - const escaper = args.escaper; appendSql(`t.${escaper(o.value)}`, args); } else { args.push(o.value); From d1007b39831d258e3923682b1a65b591f01d5452 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Fri, 4 Nov 2022 18:03:38 -0400 Subject: [PATCH 17/20] handling no columns correctly --- src/table.mjs | 8 ++++---- test/table-test.mjs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/table.mjs b/src/table.mjs index 219a5f11..3d361e6a 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -256,11 +256,11 @@ export function makeQueryTemplate(operations, source) { } if (source.dialect === "mssql") { if (slice.to !== null || slice.from !== null) { - if (!sort.length) { - if (columns[0] === "*") - throw new Error( + if (columns === "*") + throw new Error( "at least one column must be explicitly specified. Received '*'." - ); + ); + if (!sort.length) { appendSql(`\nORDER BY `, args); appendOrderBy( {column: select.columns[0], direction: "ASC"}, diff --git a/test/table-test.mjs b/test/table-test.mjs index 0f3b2716..100e45f6 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -335,7 +335,7 @@ describe("makeQueryTemplate", () => { const operations = { ...baseOperations, select: { - columns: [] + columns: null }, sort: [{column: "col2", direction: "desc"}], slice: {from: 10, to: 100}, From 669c86b158202d26b12b8ebec1f1a66b510ffeee Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Fri, 4 Nov 2022 18:06:06 -0400 Subject: [PATCH 18/20] use select.columns explicitely --- src/table.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/table.mjs b/src/table.mjs index 3d361e6a..d6a60761 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -256,7 +256,7 @@ export function makeQueryTemplate(operations, source) { } if (source.dialect === "mssql") { if (slice.to !== null || slice.from !== null) { - if (columns === "*") + if (!select.columns) throw new Error( "at least one column must be explicitly specified. Received '*'." ); From 567231f79e0f757aa7e29faf3f3108992f5d5226 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Fri, 4 Nov 2022 18:20:20 -0400 Subject: [PATCH 19/20] make right query if no column but sorter specified --- src/table.mjs | 9 +++++---- test/table-test.mjs | 36 ++++++++++++++++++------------------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/table.mjs b/src/table.mjs index d6a60761..70c4b284 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -256,11 +256,12 @@ export function makeQueryTemplate(operations, source) { } if (source.dialect === "mssql") { if (slice.to !== null || slice.from !== null) { - if (!select.columns) - throw new Error( - "at least one column must be explicitly specified. Received '*'." - ); + if (!sort.length) { + if (!select.columns) + throw new Error( + "at least one column must be explicitly specified. Received '*'." + ); appendSql(`\nORDER BY `, args); appendOrderBy( {column: select.columns[0], direction: "ASC"}, diff --git a/test/table-test.mjs b/test/table-test.mjs index 100e45f6..7b3184e3 100644 --- a/test/table-test.mjs +++ b/test/table-test.mjs @@ -337,30 +337,30 @@ describe("makeQueryTemplate", () => { select: { columns: null }, - sort: [{column: "col2", direction: "desc"}], - slice: {from: 10, to: 100}, - filter: [ - { - type: "gte", - operands: [ - {type: "column", value: "col1"}, - {type: "resolved", value: "val1"} - ] - }, - { - type: "eq", - operands: [ - {type: "column", value: "col2"}, - {type: "resolved", value: "val2"} - ] - } - ] + sort: [], + slice: {from: 10, to: 100} }; assert.throws(() => { makeQueryTemplate(operations, source); }, Error); }); + + it("makeQueryTemplate the sort and slice if no columns are explicitly BUT sort has value for mssql dialect", () => { + const source = {name: "db", dialect: "mssql"}; + const operations = { + ...baseOperations, + select: { + columns: null + }, + sort: [{column: "col2", direction: "desc"}], + slice: {from: 10, to: 100} + }; + + const [parts, ...params] = makeQueryTemplate(operations, source); + assert.deepStrictEqual(parts.join("?"), "SELECT * FROM table1 t\nORDER BY t.col2 DESC\nOFFSET 10 ROWS\nFETCH NEXT 90 ROWS ONLY"); + assert.deepStrictEqual(params, []); + }); }); describe("__table", () => { From 557c18dfa66d6ffc38e7de6afacbceb8b40eec61 Mon Sep 17 00:00:00 2001 From: Sylvestre Gug Date: Fri, 4 Nov 2022 18:21:04 -0400 Subject: [PATCH 20/20] prettier --- src/table.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/table.mjs b/src/table.mjs index 70c4b284..ec4aa9cf 100644 --- a/src/table.mjs +++ b/src/table.mjs @@ -256,7 +256,6 @@ export function makeQueryTemplate(operations, source) { } if (source.dialect === "mssql") { if (slice.to !== null || slice.from !== null) { - if (!sort.length) { if (!select.columns) throw new Error(