-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
279 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
### `updateDistinct` | ||
|
||
```js | ||
import { | ||
updateDistinct | ||
} from 'slonik-utilities'; | ||
|
||
/** | ||
* @param connection Instance of Slonik connection. | ||
* @param {string} tableName Target table name. | ||
* @param {Object.<string, ValueExpression>} namedValueBindings Object describing the desired column values. | ||
* @param {Object.<string, EqualPredicate>} [booleanExpressionValues] Object describing the boolean expression used to construct WHERE condition. | ||
*/ | ||
updateDistinct; | ||
|
||
``` | ||
|
||
Constructs and executes `UPDATE` query matching only rows with distinct values. | ||
|
||
#### Example: Update all rows | ||
|
||
Operation: | ||
|
||
```js | ||
update( | ||
connection, | ||
'user', | ||
{ | ||
givenName: 'foo' | ||
} | ||
); | ||
|
||
``` | ||
|
||
Is equivalent to: | ||
|
||
```sql | ||
UPDATE "user" | ||
SET | ||
"given_name" = $1 | ||
WHERE | ||
"given_name" IS DISTINCT FROM $1; | ||
|
||
``` | ||
|
||
#### Example: Update rows matching a boolean WHERE condition | ||
|
||
Operation: | ||
|
||
```js | ||
update( | ||
connection, | ||
'user', | ||
{ | ||
givenName: 'foo' | ||
}, | ||
{ | ||
lastName: 'bar' | ||
} | ||
); | ||
|
||
``` | ||
|
||
Is equivalent to: | ||
|
||
```sql | ||
UPDATE "user" | ||
SET | ||
"given_name" = $1 | ||
WHERE | ||
"last_name" = $2 AND | ||
"given_name" IS DISTINCT FROM $1; | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,6 @@ | |
|
||
export { | ||
update, | ||
updateDistinct, | ||
upsert | ||
} from './routines'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
// @flow | ||
|
||
export {default as update} from './update'; | ||
export {default as updateDistinct} from './updateDistinct'; | ||
export {default as upsert} from './upsert'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// @flow | ||
|
||
import { | ||
normalizeIdentifier, | ||
sql | ||
} from 'slonik'; | ||
import type { | ||
DatabaseConnectionType, | ||
ValueExpressionType | ||
} from 'slonik'; | ||
|
||
type NamedValueBindingsType = { | ||
+[key: string]: ValueExpressionType | ||
}; | ||
|
||
export default async ( | ||
connection: DatabaseConnectionType, | ||
tableName: string, | ||
namedValueBindings: NamedValueBindingsType, | ||
|
||
// eslint-disable-next-line flowtype/no-weak-types | ||
booleanExpressionValues: Object = null | ||
) => { | ||
const assignmentList = sql.assignmentList(namedValueBindings); | ||
|
||
let booleanExpression = sql.booleanExpression( | ||
Object | ||
.entries(namedValueBindings) | ||
.map(([key, value]) => { | ||
// $FlowFixMe | ||
return sql.raw('$1 IS DISTINCT FROM $2', [sql.identifier([normalizeIdentifier(key)]), value]); | ||
}), | ||
'OR' | ||
); | ||
|
||
if (booleanExpressionValues) { | ||
booleanExpression = sql.booleanExpression( | ||
[ | ||
booleanExpression, | ||
sql.booleanExpression( | ||
Object | ||
.entries(booleanExpressionValues) | ||
.map(([key, value]) => { | ||
// $FlowFixMe | ||
return sql.comparisonPredicate(sql.identifier([key]), '=', value); | ||
}), | ||
'AND' | ||
) | ||
], | ||
'AND' | ||
); | ||
} | ||
|
||
await connection.query(sql` | ||
UPDATE ${sql.identifier([tableName])} | ||
SET ${assignmentList} | ||
WHERE ${booleanExpression} | ||
`); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// @flow | ||
|
||
import { | ||
sql | ||
} from 'slonik'; | ||
import test from 'ava'; | ||
import sinon from 'sinon'; | ||
import updateDistinct from '../../../src/routines/updateDistinct'; | ||
import normalizeQuery from '../../helpers/normalizeQuery'; | ||
|
||
const createConnection = () => { | ||
const query = sinon.stub(); | ||
|
||
const connection = { | ||
query | ||
}; | ||
|
||
return connection; | ||
}; | ||
|
||
test('executes UPDATE query without WHERE condition (single column)', async (t) => { | ||
const connection = createConnection(); | ||
|
||
await updateDistinct( | ||
connection, | ||
'foo', | ||
{ | ||
bar: 'baz' | ||
} | ||
); | ||
|
||
t.is(connection.query.callCount, 1); | ||
|
||
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar" = $1 WHERE ("bar" IS DISTINCT FROM $2)'); | ||
t.deepEqual(connection.query.firstCall.args[0].values, [ | ||
'baz', | ||
'baz' | ||
]); | ||
}); | ||
|
||
test('executes UPDATE query without WHERE condition (multiple columns)', async (t) => { | ||
const connection = createConnection(); | ||
|
||
await updateDistinct( | ||
connection, | ||
'foo', | ||
{ | ||
bar0: 'baz0', | ||
bar1: 'baz1', | ||
bar2: 'baz2' | ||
} | ||
); | ||
|
||
t.is(connection.query.callCount, 1); | ||
|
||
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar_0" = $1, "bar_1" = $2, "bar_2" = $3 WHERE ("bar_0" IS DISTINCT FROM $4 OR "bar_1" IS DISTINCT FROM $5 OR "bar_2" IS DISTINCT FROM $6)'); | ||
t.deepEqual(connection.query.firstCall.args[0].values, [ | ||
'baz0', | ||
'baz1', | ||
'baz2', | ||
'baz0', | ||
'baz1', | ||
'baz2' | ||
]); | ||
}); | ||
|
||
test('executes UPDATE query without WHERE condition (SQL token)', async (t) => { | ||
const connection = createConnection(); | ||
|
||
await updateDistinct( | ||
connection, | ||
'foo', | ||
{ | ||
bar0: 'baz0', | ||
bar1: sql.raw('to_timestamp($1)', ['baz1']), | ||
bar2: 'baz2' | ||
} | ||
); | ||
|
||
t.is(connection.query.callCount, 1); | ||
|
||
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar_0" = $1, "bar_1" = to_timestamp($2), "bar_2" = $3 WHERE ("bar_0" IS DISTINCT FROM $4 OR "bar_1" IS DISTINCT FROM to_timestamp($5) OR "bar_2" IS DISTINCT FROM $6)'); | ||
t.deepEqual(connection.query.firstCall.args[0].values, [ | ||
'baz0', | ||
'baz1', | ||
'baz2', | ||
'baz0', | ||
'baz1', | ||
'baz2' | ||
]); | ||
}); | ||
|
||
test('executes UPDATE query with WHERE condition (AND boolean expression short-hand; single comparison predicate)', async (t) => { | ||
const connection = createConnection(); | ||
|
||
await updateDistinct( | ||
connection, | ||
'foo', | ||
{ | ||
bar: 'baz' | ||
}, | ||
{ | ||
qux: 'quux' | ||
} | ||
); | ||
|
||
t.is(connection.query.callCount, 1); | ||
|
||
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar" = $1 WHERE (("bar" IS DISTINCT FROM $2) AND ("qux" = $3))'); | ||
t.deepEqual(connection.query.firstCall.args[0].values, [ | ||
'baz', | ||
'baz', | ||
'quux' | ||
]); | ||
}); |
Oops, something went wrong.