-
-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve the way library deals with objects in WHERE queries (WIP) #106
base: master
Are you sure you want to change the base?
Conversation
vals
and cols
helpers
vals
and cols
helpers
I've also run into problems with objects that have some keys mapped to undefined. Apart from But I see I potential bug with this patch. This patch removes undefined keys from The problem I'm thinking about is with bulk insert or upsert statements. For these statements there is an array of objects, each of which, represents a different item to insert in the DB. Lines 241 to 244 in e2cbc70
Perhaps this can be solved by patching I'm thinking about something like this /**
* Removes undefined keys from an object.
*
* @param obj The object to trim
*/
export const trimObj = <T extends object>(obj: T): T => {
const definedEntries = Object.entries(obj).filter(([, value]) => value !== undefined);
return Object.fromEntries(definedEntries) as T;
};
export const completeKeysWithDefaultValue = <T extends object>(objs: readonly T[], defaultValue: any): T[] => {
const trimmedObjs: readonly T[] = objs.map(obj => trimObj(obj));
const unionKeys: T = Object.assign({}, ...trimmedObjs);
for (const k in unionKeys) unionKeys[k] = defaultValue;
return trimmedObjs.map(o => ({ ...unionKeys, ...o }));
}; |
src/db/core.ts
Outdated
@@ -383,11 +383,22 @@ export class SQLFragment<RunResult = pg.QueryResult['rows'], Constraint = never> | |||
columnNames = <Column[]>Object.keys(expression.value).sort(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block could be simplified if the columns are filtered here. Then there is no need for firstField
columnNames = <Column[]>Object.keys(expression.value)
.filter(key => (<any>expression.value)[key] !== undefined)
.sort(),
Or with trimObj
defined above
columnNames = <Column[]>Object.keys(trimObj(expression.value)).sort(),
src/db/core.ts
Outdated
@@ -400,19 +411,31 @@ export class SQLFragment<RunResult = pg.QueryResult['rows'], Constraint = never> | |||
const columnNames = <Column[]>Object.keys(expression).sort(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block could be simplified if the columns are filtered here. Then there is no need for firstField
const columnNames = <Column[]>Object.keys(expression)
.filter(key => (<any>expression)[key] !== undefined)
.sort();
Or with trimObj
defined above
const columnNames = <Column[]>Object.keys(trimObj(expression)).sort();
@javier-garcia-meteologica The problem is, since this library doesn't have a test suite, I was basically making this blindly, with kind of limited testing (using local NPM install tricks). I've since done the following:
Based on some actual usage experience, I've given up on trying to make this work with cols() and vals(). Since they are called completely separately, there is no good obvious way to ensure that the kind of arguments you give each are properly aligned. I just ended up dealing with non-obvious errors. I have different ideas for cols() and vals(), but I'll test them out and, if they turn out good, I'll submit a separate PR. The I've updated the PR to include the fixed code. I'll keep using my cloned package to test this further, and update this PR (or create a new one) with the improvements I find usable. |
A month ago I added the patch to ignore undefined keys in the branch javier-garcia-meteologica/zapatos#ready, the commit is named Ignore undefined keys. On top of I haven't run into any bugs, so I guess that was all that had to be changed. Previously I had been using the workaround suggested in #46, but the problem was that typescript didn't warn me when I used an undefined key. res = await db.select('cats', {
...(breed ? { breed } : {}),
age, // Mistake! age is undefined but typescript doesn't warn me
...(name ? { name } : {}),
otherCharacteristic: true
}).run(pgPool); Using the patch mentioned above, it becomes easier to use. There is no need to worry about undefined values because they will be treated as if the key didn't exist, which is the same criteria that typescript uses to type inputs. res = await db.select('cats', {
breed,
name,
age,
otherCharacteristic: true
}).run(pgPool); Another solution is the one suggested in #97, which uses |
@javier-garcia-meteologica Primary reason why I gave up on function addUser(user: User) {
db.sql`INSERT INTO ${'users'} (${db.cols(User.KEYS)}) VALUES (${db.vals(user)})`
} If some of the keys in user were undefined, it would break during runtime. Sure, everything would work if I used Another gotcha in your implementation is that, if all keys turn out |
Let me convince you why your original idea of ignoring
That's an argument against Zapatos already expects the same object to be passed to both
In the case of where object conditions, it compiles just fine, the same as if I used an empty object with the original // With javier-garcia-meteologica/zapatos#ready
zp.sql`SELECT * FROM ${'table_foo'} WHERE ${{ foo: undefined }}`.compile()
// { text: 'SELECT * FROM "table_foo" WHERE TRUE', values: [] }
// With original jawj/zapatos
zp.sql`SELECT * FROM ${'table_foo'} WHERE ${{}}`.compile()
// { text: 'SELECT * FROM "table_foo" WHERE TRUE', values: [] } In the case of insert columns and values, it can generate invalid insert statements, just as if you passed an empty object to the original // With javier-garcia-meteologica/zapatos#ready
const input = { foo: undefined };
zp.sql`INSERT INTO ${'table_foo'} (${zp.cols(input)}) VALUES (${zp.vals(input)})`.compile()
// { text: 'INSERT INTO "table_foo" () VALUES ()', values: [] }
// With original jawj/zapatos
const input = {};
zp.sql`INSERT INTO ${'table_foo'} (${zp.cols(input)}) VALUES (${zp.vals(input)})`.compile()
// { text: 'INSERT INTO "table_foo" () VALUES ()', values: [] }
// With original jawj/zapatos
const input = { foo: undefined };
zp.sql`INSERT INTO ${'table_foo'} (${zp.cols(input)}) VALUES (${zp.vals(input)})`.compile()
// { text: 'INSERT INTO "table_foo" ("foo") VALUES ($1)', values: [undefined] } |
Changes:
undefined
properties in objects you provide to WHERE queries. They are now ignored.My use case for this PR:
NOTE: I've updated this PR multiple times, and commit history is a bit messy. If there is interest to merge it, I'll gladly rebase the code.