Skip to content

Commit

Permalink
Issue #54. Comparisons SHOULD BE case insensitive.
Browse files Browse the repository at this point in the history
All condition comparisons were case sensitive.  My expected data from various unit tests were not compared to actual mySQL results - so it was not working as expected in a SQL environment.
  • Loading branch information
demmings committed Mar 15, 2024
1 parent 500035e commit 1504e47
Show file tree
Hide file tree
Showing 9 changed files with 4,853 additions and 4,749 deletions.
9,452 changes: 4,754 additions & 4,698 deletions coverage/tests.lcov

Large diffs are not rendered by default.

48 changes: 28 additions & 20 deletions dist/gssql.js
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,7 @@ class Table { // skipcq: JS-0128
value = (value !== null) ? value.toString() : value;

if (value !== "") {
value = typeof value === 'string' ? value.toUpperCase() : value;
const rowNumbers = fieldValuesMap.has(value) ? fieldValuesMap.get(value) : [];
rowNumbers.push(i);

Expand Down Expand Up @@ -1926,8 +1927,8 @@ class SelectTables {
}
if (logic === "OR") {
results = Array.from(new Set(recordIDs.reduce((a, b) => a.concat(b))));
}
}

return results;
}

Expand Down Expand Up @@ -2632,55 +2633,61 @@ class FieldComparisons {
static getComparisonFunction(operator) {
switch (operator.toUpperCase()) {
case "=":
return (leftValue, rightValue) => { return leftValue == rightValue }; // skipcq: JS-0050
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue == rightValue }; // skipcq: JS-0050

case ">":
return (leftValue, rightValue) => { return leftValue > rightValue };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue > rightValue };

case "<":
return (leftValue, rightValue) => { return leftValue < rightValue };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue < rightValue };

case ">=":
return (leftValue, rightValue) => { return leftValue >= rightValue };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue >= rightValue };

case "<=":
return (leftValue, rightValue) => { return leftValue <= rightValue };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue <= rightValue };

case "<>":
return (leftValue, rightValue) => { return leftValue != rightValue }; // skipcq: JS-0050
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue != rightValue }; // skipcq: JS-0050

case "!=":
return (leftValue, rightValue) => { return leftValue != rightValue }; // skipcq: JS-0050
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue != rightValue }; // skipcq: JS-0050

case "LIKE":
return (leftValue, rightValue) => { return FieldComparisons.likeCondition(leftValue, rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.likeCondition(leftValue, rightValue) };

case "NOT LIKE":
return (leftValue, rightValue) => { return FieldComparisons.notLikeCondition(leftValue, rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.notLikeCondition(leftValue, rightValue) };

case "IN":
return (leftValue, rightValue) => { return FieldComparisons.inCondition(leftValue, rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.inCondition(leftValue, rightValue) };

case "NOT IN":
return (leftValue, rightValue) => { return !(FieldComparisons.inCondition(leftValue, rightValue)) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return !(FieldComparisons.inCondition(leftValue, rightValue)) };

case "IS NOT":
return (leftValue, rightValue) => { return !(FieldComparisons.isCondition(leftValue, rightValue)) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return !(FieldComparisons.isCondition(leftValue, rightValue)) };

case "IS":
return (leftValue, rightValue) => { return FieldComparisons.isCondition(leftValue, rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.isCondition(leftValue, rightValue) };

case "EXISTS":
return (leftValue, rightValue) => { return FieldComparisons.existsCondition(rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.existsCondition(rightValue) };

case "NOT EXISTS":
return (leftValue, rightValue) => { return !(FieldComparisons.existsCondition(rightValue)) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return !(FieldComparisons.existsCondition(rightValue)) };

default:
throw new Error(`Invalid Operator: ${operator}`);
}
}

static parmsToUpperCase(leftValue, rightValue) {
leftValue = typeof leftValue === 'string' ? leftValue.toUpperCase() : leftValue;
rightValue = typeof rightValue === 'string' ? rightValue.toUpperCase() : rightValue;
return [leftValue, rightValue];
}

/**
* Compare strings in LIKE condition
* @param {String} leftValue - string for comparison
Expand Down Expand Up @@ -2741,7 +2748,7 @@ class FieldComparisons {
items = [rightValue.toString()];
}

items = items.map(a => a.trim());
// items = items.map(a => a.trim());

let index = items.indexOf(leftValue);
if (index === -1 && typeof leftValue === 'number') {
Expand Down Expand Up @@ -4041,7 +4048,7 @@ class AggregateTrack {
if (columnData === null) {
return this.groupValue;
}

this.groupValue++;
if (this.isDistinct) {
this.distinctSet.add(columnData);
Expand Down Expand Up @@ -5356,6 +5363,7 @@ class JoinTablesRecordIds {
let keyMasterJoinField = null;
for (let leftTableRecordNum = 1; leftTableRecordNum < leftTableData.length; leftTableRecordNum++) {
keyMasterJoinField = this.getJoinColumnData(leftField, leftTableRecordNum);
keyMasterJoinField = typeof keyMasterJoinField === 'string' ? keyMasterJoinField.toUpperCase() : keyMasterJoinField;

const joinRows = !keyFieldMap.has(keyMasterJoinField) ? [] : keyFieldMap.get(keyMasterJoinField);

Expand Down Expand Up @@ -6322,7 +6330,7 @@ class CondParser {
astNode += ` ${inCurrentToken.value}`;
}
else {
astNode += `, ${inCurrentToken.value}`;
astNode += `,${inCurrentToken.value}`;
}

inCurrentToken = this.currentToken;
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@demmings/gssql",
"version": "1.3.34",
"version": "1.3.35",
"description": "Google Sheets QUERY function replacement using real SQL select syntax.",
"main": "testGsSql.js",
"files": ["./src", "src", "img", "dist"],
Expand Down
1 change: 1 addition & 0 deletions src/JoinTables.js
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ class JoinTablesRecordIds {
let keyMasterJoinField = null;
for (let leftTableRecordNum = 1; leftTableRecordNum < leftTableData.length; leftTableRecordNum++) {
keyMasterJoinField = this.getJoinColumnData(leftField, leftTableRecordNum);
keyMasterJoinField = typeof keyMasterJoinField === 'string' ? keyMasterJoinField.toUpperCase() : keyMasterJoinField;

const joinRows = !keyFieldMap.has(keyMasterJoinField) ? [] : keyFieldMap.get(keyMasterJoinField);

Expand Down
2 changes: 1 addition & 1 deletion src/SimpleParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ class CondParser {
astNode += ` ${inCurrentToken.value}`;
}
else {
astNode += `, ${inCurrentToken.value}`;
astNode += `,${inCurrentToken.value}`;
}

inCurrentToken = this.currentToken;
Expand Down
48 changes: 40 additions & 8 deletions src/SqlTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,7 @@ class SqlTester {
whereIn2() {
let stmt = "SELECT books.id, books.title, books.author_id " +
"FROM books " +
"WHERE books.author_id IN ('11','12') " +
"WHERE books.author_id IN ('11', '12') " +
"ORDER BY books.title";

let data = new TestSql()
Expand Down Expand Up @@ -1372,18 +1372,16 @@ class SqlTester {
}

whereIn6() {
let stmt = "SELECT * " +
"FROM editors " +
"WHERE first_name IN (' Mark ', 'Maria ', 'CATHRINE ', ' jacK') ";
let stmt = "SELECT * FROM editors WHERE first_name IN (' Mark ', 'Maria ', 'CATHRINE ', 'Jack')";

let data = new TestSql()
.addTableData("editors", this.editorsTable())
.enableColumnTitle(true)
.execute(stmt);

let expected = [["EDITORS.ID", "EDITORS.FIRST_NAME", "EDITORS.LAST_NAME"],
["22", "Mark", "Johnson"],
["23", "Maria", "Evans"]];
["13", "Jack", "Smart"],
["50", "Jack", "Dumb"]];

return this.isEqual("whereIn6", data, expected);
}
Expand Down Expand Up @@ -1455,15 +1453,13 @@ class SqlTester {
// Depending on how your SQL is configured, the comparison is either
// case or case insensitive. For gsSQL() it is case senstive.
let expected = [["EDITORS.ID", "EDITORS.FIRST_NAME", "EDITORS.LAST_NAME"],
["13", "Jack", "Smart"],
["21", "Daniel", "Brown"],
["22", "Mark", "Johnson"],
["23", "Maria", "Evans"],
["24", "Cathrine", "Roberts"],
["25", "Sebastian", "Wright"],
["26", "Barbara", "Jones"],
["27", "Matthew", "Smith"],
["50", "Jack", "Dumb"],
["51", "Daniel", "Smart"]];

return this.isEqual("whereNotIn3", data, expected);
Expand Down Expand Up @@ -4082,6 +4078,40 @@ class SqlTester {
return this.isEqual("selectCalculatedFieldWitinGroupBY", data, expected);
}

selectJoinCaseInSensitiveCondition() {
let stmt = "select customers.id, min(bookreturns.price) from bookreturns join customers on bookreturns.customer_id = customers.id group by customers.id";

let data = new TestSql()
.addTableData("customers", this.customerTable())
.addTableData("bookreturns", this.bookReturnsTable())
.enableColumnTitle(true)
.execute(stmt);

let expected = [["customers.id", "min(bookreturns.price)"],
["C1", 33.97],
["C2", 17.99],
["C3", 59.99],
["C4", 19.99]];

return this.isEqual("selectJoinCaseInSensitiveCondition", data, expected);
}

selectCaseInSensitiveCondition() {
let stmt = "select * from bookreturns where customer_id = 'C1'";

let data = new TestSql()
.addTableData("bookreturns", this.bookReturnsTable())
.enableColumnTitle(true)
.execute(stmt);

let expected = [["BOOKRETURNS.RMA", "BOOKRETURNS.BOOK_ID", "BOOKRETURNS.CUSTOMER_ID", "BOOKRETURNS.QUANTITY", "BOOKRETURNS.PRICE", "BOOKRETURNS.DATE"],
["Rma001", "9", "c1", 10, 34.95, "05/01/2022"],
["rma005", "1", "c1", 1, 90, "05/02/2022"],
["RMA900", "7", "c1", 1, 33.97, "05/04/2022"]];

return this.isEqual("selectInSensitiveCondition", data, expected);
}

// S T A R T O T H E R T E S T S
removeTrailingEmptyRecords() {
let authors = this.authorsTable();
Expand Down Expand Up @@ -5240,6 +5270,8 @@ function testerSql() {
result = result && tester.selectBetweenFromFunction();
result = result && tester.selectCountWithNullOnJoin();
// result = result && tester.selectCalculatedFieldWitinGroupBY();
result = result && tester.selectJoinCaseInSensitiveCondition();
result = result && tester.selectCaseInSensitiveCondition();

Logger.log("============================================================================");

Expand Down
1 change: 1 addition & 0 deletions src/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ class Table { // skipcq: JS-0128
value = (value !== null) ? value.toString() : value;

if (value !== "") {
value = typeof value === 'string' ? value.toUpperCase() : value;
const rowNumbers = fieldValuesMap.has(value) ? fieldValuesMap.get(value) : [];
rowNumbers.push(i);

Expand Down
44 changes: 25 additions & 19 deletions src/Views.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ class SelectTables {
}
if (logic === "OR") {
results = Array.from(new Set(recordIDs.reduce((a, b) => a.concat(b))));
}
}

return results;
}

Expand Down Expand Up @@ -862,55 +862,61 @@ class FieldComparisons {
static getComparisonFunction(operator) {
switch (operator.toUpperCase()) {
case "=":
return (leftValue, rightValue) => { return leftValue == rightValue }; // skipcq: JS-0050
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue == rightValue }; // skipcq: JS-0050

case ">":
return (leftValue, rightValue) => { return leftValue > rightValue };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue > rightValue };

case "<":
return (leftValue, rightValue) => { return leftValue < rightValue };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue < rightValue };

case ">=":
return (leftValue, rightValue) => { return leftValue >= rightValue };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue >= rightValue };

case "<=":
return (leftValue, rightValue) => { return leftValue <= rightValue };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue <= rightValue };

case "<>":
return (leftValue, rightValue) => { return leftValue != rightValue }; // skipcq: JS-0050
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue != rightValue }; // skipcq: JS-0050

case "!=":
return (leftValue, rightValue) => { return leftValue != rightValue }; // skipcq: JS-0050
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return leftValue != rightValue }; // skipcq: JS-0050

case "LIKE":
return (leftValue, rightValue) => { return FieldComparisons.likeCondition(leftValue, rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.likeCondition(leftValue, rightValue) };

case "NOT LIKE":
return (leftValue, rightValue) => { return FieldComparisons.notLikeCondition(leftValue, rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.notLikeCondition(leftValue, rightValue) };

case "IN":
return (leftValue, rightValue) => { return FieldComparisons.inCondition(leftValue, rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.inCondition(leftValue, rightValue) };

case "NOT IN":
return (leftValue, rightValue) => { return !(FieldComparisons.inCondition(leftValue, rightValue)) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return !(FieldComparisons.inCondition(leftValue, rightValue)) };

case "IS NOT":
return (leftValue, rightValue) => { return !(FieldComparisons.isCondition(leftValue, rightValue)) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return !(FieldComparisons.isCondition(leftValue, rightValue)) };

case "IS":
return (leftValue, rightValue) => { return FieldComparisons.isCondition(leftValue, rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.isCondition(leftValue, rightValue) };

case "EXISTS":
return (leftValue, rightValue) => { return FieldComparisons.existsCondition(rightValue) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return FieldComparisons.existsCondition(rightValue) };

case "NOT EXISTS":
return (leftValue, rightValue) => { return !(FieldComparisons.existsCondition(rightValue)) };
return (leftValue, rightValue) => { [leftValue, rightValue] = FieldComparisons.parmsToUpperCase(leftValue, rightValue); return !(FieldComparisons.existsCondition(rightValue)) };

default:
throw new Error(`Invalid Operator: ${operator}`);
}
}

static parmsToUpperCase(leftValue, rightValue) {
leftValue = typeof leftValue === 'string' ? leftValue.toUpperCase() : leftValue;
rightValue = typeof rightValue === 'string' ? rightValue.toUpperCase() : rightValue;
return [leftValue, rightValue];
}

/**
* Compare strings in LIKE condition
* @param {String} leftValue - string for comparison
Expand Down Expand Up @@ -971,7 +977,7 @@ class FieldComparisons {
items = [rightValue.toString()];
}

items = items.map(a => a.trim());
// items = items.map(a => a.trim());

let index = items.indexOf(leftValue);
if (index === -1 && typeof leftValue === 'number') {
Expand Down Expand Up @@ -2271,7 +2277,7 @@ class AggregateTrack {
if (columnData === null) {
return this.groupValue;
}

this.groupValue++;
if (this.isDistinct) {
this.distinctSet.add(columnData);
Expand Down

0 comments on commit 1504e47

Please sign in to comment.