Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 45 additions & 21 deletions src/table.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -248,20 +248,44 @@ export function makeQueryTemplate(operations, source) {
];
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 (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 (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,
escaper
);
}
appendSql(`\nOFFSET ${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;
}
Expand All @@ -282,16 +306,16 @@ function appendSql(sql, args) {
strings[strings.length - 1] += sql;
}

function appendOrderBy({column, direction}, args) {
appendSql(`t.${column} ${direction.toUpperCase()}`, args);
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);
Expand All @@ -310,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);
Expand All @@ -319,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);
Expand All @@ -345,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);
Expand All @@ -366,9 +390,9 @@ function appendWhereEntry({type, operands}, args) {
appendSql(")", args);
}

function appendOperand(o, args) {
function appendOperand(o, args, escaper) {
if (o.type === "column") {
appendSql(`t.${o.value}`, args);
appendSql(`t.${escaper(o.value)}`, args);
} else {
args.push(o.value);
args[0].push("");
Expand Down
146 changes: 146 additions & 0 deletions test/table-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,53 @@ 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 and escape filters column only once", () => {
const source = {name: "db", dialect: "postgres", escape: (i) => `_${i}_`};
const operations = {
...baseOperations,
filter: [
{
type: "eq",
operands: [
{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._col2_ = ?"
);
assert.deepStrictEqual(params, ["val1"]);
});

it("makeQueryTemplate filter list", () => {
const source = {name: "db", dialect: "postgres"};
const operations = {
Expand Down Expand Up @@ -164,6 +211,24 @@ 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};
Expand Down Expand Up @@ -215,6 +280,87 @@ 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 and escape column name with mssql syntax", () => {
const source = {name: "db", dialect: "mssql", escape: (i) => `_${i}_`};
const operations = {
...baseOperations,
select: {
columns: ["col1", "col2", "col3"]
},
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 0 ROWS\nFETCH NEXT 100 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"]);
});

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", () => {
Expand Down