Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: gajus/slonik
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v32.0.0
Choose a base ref
...
head repository: gajus/slonik
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v33.0.0
Choose a head ref
  • 2 commits
  • 61 files changed
  • 1 contributor

Commits on Nov 9, 2022

  1. chore: update dependencies

    gajus committed Nov 9, 2022
    Copy the full SHA
    8de6b04 View commit details
  2. Distinguish between SQL query and fragment (#429)

    BREAKING CHANGE:
    
    ### Replaces `sql` tagged template literal with `sql.fragment`.
    
    Before:
    
    ```ts
    const where = sql`WHERE bar = 1`
    
    sql`
      SELECT 1
      FROM foo
      ${where}
    `
    ```
    
    After:
    
    ```ts
    const where = sql.fragment`WHERE bar = 1`
    
    sql.fragment`
      SELECT 1
      FROM foo
      ${where}
    `
    ```
    
    ### SQL fragments cannot be used as queries; use `sql.type` instead
    
    Before:
    
    ```ts
    connection.query(sql`
      SELECT 1
      FROM foo
    `)
    ```
    
    After:
    
    ```ts
    connection.query(sql.type({
      id: z.object({
        id: z.number(),
      })
    })`
      SELECT 1 AS id
      FROM foo
    `)
    ```
    
    ### Adds `sql.unsafe` (shortcut to `sql.type(z.any())`)
    
    Before:
    
    ```ts
    connection.query(sql`
      SELECT 1
      FROM foo
    `)
    ```
    
    After:
    
    ```ts
    connection.query(sql.unsafe`
      SELECT 1
      FROM foo
    `)
    ```
    
    ### Query methods can no longer by typed, i.e. `connection.one<Row>()` needs to be replaced with `connection.one(sql.type()``)`.
    
    Before:
    
    ```ts
    connection.oneFirst<number>(sql`
      SELECT 1
      FROM foo
    `)
    ```
    
    After:
    
    ```ts
    connection.oneFirst(sql.typeAlias('id')`
      SELECT 1 AS id
      FROM foo
    `)
    ```
    gajus authored Nov 9, 2022
    Copy the full SHA
    d2f62c8 View commit details
Showing with 1,246 additions and 1,655 deletions.
  1. +16 −16 .README/ABOUT_SLONIK.md
  2. +8 −8 .README/ERROR_HANDLING.md
  3. +123 −39 .README/QUERY_BUILDING.md
  4. +27 −25 .README/QUERY_METHODS.md
  5. +5 −5 .README/RECIPES.md
  6. +1 −1 .README/RUNTIME_VALIDATION.md
  7. +21 −26 .README/SQL_TAG.md
  8. +2 −26 .README/TYPES.md
  9. +9 −9 .README/USAGE.md
  10. +6 −6 .README/VALUE_PLACEHOLDERS.md
  11. +223 −161 README.md
  12. +192 −89 package-lock.json
  13. +1 −1 package.json
  14. +0 −12 src/assertions.ts
  15. +10 −18 src/binders/bindPool.ts
  16. +0 −27 src/binders/bindPoolConnection.ts
  17. +0 −14 src/binders/bindTransactionConnection.ts
  18. +2 −2 src/connectionMethods/exists.ts
  19. +2 −2 src/factories/createConnection.ts
  20. +143 −167 src/factories/createSqlTag.ts
  21. +8 −4 src/factories/createSqlTokenSqlFragment.ts
  22. +3 −4 src/index.ts
  23. +7 −11 src/routines/executeQuery.ts
  24. +2 −2 src/sqlFragmentFactories/createArraySqlFragment.ts
  25. +41 −0 src/sqlFragmentFactories/createFragmentSqlFragment.ts
  26. +2 −2 src/sqlFragmentFactories/{createSqlSqlFragment.ts → createQuerySqlFragment.ts}
  27. +5 −2 src/sqlFragmentFactories/index.ts
  28. +13 −12 src/tokens.ts
  29. +59 −59 src/types.ts
  30. +4 −2 src/utilities/isSqlToken.ts
  31. +0 −540 test/dts.ts
  32. +70 −70 test/helpers/createIntegrationTests.ts
  33. +5 −5 test/slonik/binders/bindPool/connect.ts
  34. +1 −1 test/slonik/binders/bindPool/interceptors/afterPoolConnection.ts
  35. +12 −12 test/slonik/binders/bindPool/transaction.ts
  36. +3 −3 test/slonik/connectionMethods/anyFirst.ts
  37. +2 −2 test/slonik/connectionMethods/many.ts
  38. +3 −3 test/slonik/connectionMethods/manyFirst.ts
  39. +3 −3 test/slonik/connectionMethods/maybeOne.ts
  40. +4 −4 test/slonik/connectionMethods/maybeOneFirst.ts
  41. +3 −3 test/slonik/connectionMethods/one.ts
  42. +4 −4 test/slonik/connectionMethods/oneFirst.ts
  43. +2 −2 test/slonik/connectionMethods/query/interceptors/beforeQueryExecution.ts
  44. +1 −1 test/slonik/connectionMethods/query/interceptors/transformQuery.ts
  45. +1 −1 test/slonik/connectionMethods/query/interceptors/transformRow.ts
  46. +9 −9 test/slonik/connectionMethods/query/query.ts
  47. +6 −6 test/slonik/factories/createMockPool.ts
  48. +21 −19 test/slonik/integration/pg.ts
  49. +6 −6 test/slonik/routines/executeQuery.ts
  50. +16 −16 test/slonik/templateTags/sql/array.ts
  51. +4 −4 test/slonik/templateTags/sql/date.ts
  52. +6 −6 test/slonik/templateTags/sql/identifier.ts
  53. +4 −4 test/slonik/templateTags/sql/interval.ts
  54. +33 −33 test/slonik/templateTags/sql/join.ts
  55. +17 −17 test/slonik/templateTags/sql/json.ts
  56. +17 −17 test/slonik/templateTags/sql/jsonb.ts
  57. +3 −3 test/slonik/templateTags/sql/literalValue.ts
  58. +13 −13 test/slonik/templateTags/sql/sql.ts
  59. +4 −4 test/slonik/templateTags/sql/timestamp.ts
  60. +20 −19 test/slonik/templateTags/sql/unnest.ts
  61. +18 −73 test/types.ts
32 changes: 16 additions & 16 deletions .README/ABOUT_SLONIK.md
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ import {
type DatabaseRecordIdType = number;

const getFooIdByBar = async (connection: DatabaseConnection, bar: string): Promise<DatabaseRecordIdType> => {
const fooResult = await connection.query(sql`
const fooResult = await connection.query(sql.typeAlias('id')`
SELECT id
FROM foo
WHERE bar = ${bar}
@@ -51,7 +51,7 @@ const getFooIdByBar = async (connection: DatabaseConnection, bar: string): Promi

```ts
const getFooIdByBar = (connection: DatabaseConnection, bar: string): Promise<DatabaseRecordIdType> => {
return connection.oneFirst(sql`
return connection.oneFirst(sql.typeAlias('id')`
SELECT id
FROM foo
WHERE bar = ${bar}
@@ -70,13 +70,13 @@ In the absence of helper methods, the overhead of repeating code becomes particu
Furthermore, using methods that guarantee the shape of the results allows us to leverage static type checking and catch some of the errors even before executing the code, e.g.

```ts
const fooId = await connection.many(sql`
const fooId = await connection.many(sql.typeAlias('id')`
SELECT id
FROM foo
WHERE bar = ${bar}
`);

await connection.query(sql`
await connection.query(sql.typeAlias('void')`
DELETE FROM baz
WHERE foo_id = ${fooId}
`);
@@ -96,7 +96,7 @@ The primary reason for implementing _only_ this connection pooling method is bec
const main = async () => {
const connection = await pool.connect();

await connection.query(sql`SELECT foo()`);
await connection.query(sql.typeAlias('foo')`SELECT foo()`);

await connection.release();
};
@@ -115,7 +115,7 @@ const main = async () => {
let lastExecutionResult;

try {
lastExecutionResult = await connection.query(sql`SELECT foo()`);
lastExecutionResult = await connection.query(sql.typeAlias('foo')`SELECT foo()`);
} finally {
await connection.release();
}
@@ -129,7 +129,7 @@ Slonik abstracts the latter pattern into `pool#connect()` method.
```ts
const main = () => {
return pool.connect((connection) => {
return connection.query(sql`SELECT foo()`);
return connection.query(sql.typeAlias('foo')`SELECT foo()`);
});
};
```
@@ -142,8 +142,8 @@ Just like in the [unsafe connection handling](#protecting-against-unsafe-connect

```ts
connection.transaction(async (transactionConnection) => {
await transactionConnection.query(sql`INSERT INTO foo (bar) VALUES ('baz')`);
await transactionConnection.query(sql`INSERT INTO qux (quux) VALUES ('quuz')`);
await transactionConnection.query(sql.typeAlias('void')`INSERT INTO foo (bar) VALUES ('baz')`);
await transactionConnection.query(sql.typeAlias('void')`INSERT INTO qux (quux) VALUES ('quuz')`);
});
```

@@ -188,35 +188,35 @@ The above invocation would produce an error:
This means that the only way to run a query is by constructing it using [`sql` tagged template literal](https://github.com/gajus/slonik#slonik-value-placeholders-tagged-template-literals), e.g.

```ts
connection.query(sql`SELECT 1`);
connection.query(sql.unsafe`SELECT 1`);
```

To add a parameter to the query, user must use [template literal placeholders](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Description), e.g.

```ts
connection.query(sql`SELECT ${userInput}`);
connection.query(sql.unsafe`SELECT ${userInput}`);
```

Slonik takes over from here and constructs a query with value bindings, and sends the resulting query text and parameters to PostgreSQL. There is no other way of passing parameters to the query – this adds a strong layer of protection against accidental unsafe user input handling due to limited knowledge of the SQL client API.

As Slonik restricts user's ability to generate and execute dynamic SQL, it provides helper functions used to generate fragments of the query and the corresponding value bindings, e.g. [`sql.identifier`](#sqlidentifier), [`sql.join`](#sqljoin) and [`sql.unnest`](#sqlunnest). These methods generate tokens that the query executor interprets to construct a safe query, e.g.

```ts
connection.query(sql`
connection.query(sql.unsafe`
SELECT ${sql.identifier(['foo', 'a'])}
FROM (
VALUES
(
${sql.join(
[
sql.join(['a1', 'b1', 'c1'], sql`, `),
sql.join(['a2', 'b2', 'c2'], sql`, `)
sql.join(['a1', 'b1', 'c1'], sql.fragment`, `),
sql.join(['a2', 'b2', 'c2'], sql.fragment`, `)
],
sql`), (`
sql.fragment`), (`
)}
)
) foo(a, b, c)
WHERE foo.b IN (${sql.join(['c1', 'a2'], sql`, `)})
WHERE foo.b IN (${sql.join(['c1', 'a2'], sql.fragment`, `)})
`);
```

16 changes: 8 additions & 8 deletions .README/ERROR_HANDLING.md
Original file line number Diff line number Diff line change
@@ -34,14 +34,14 @@ If you require to extract meta-data about a specific type of error (e.g. constra
await pool.connect(async (connection0) => {
try {
await pool.connect(async (connection1) => {
const backendProcessId = await connection1.oneFirst(sql`SELECT pg_backend_pid()`);
const backendProcessId = await connection1.oneFirst(sql.typeAlias('id')`SELECT pg_backend_pid() AS id`);

setTimeout(() => {
connection0.query(sql`SELECT pg_cancel_backend(${backendProcessId})`)
connection0.query(sql.typeAlias('void')`SELECT pg_cancel_backend(${backendProcessId})`)
}, 2000);

try {
await connection1.query(sql`SELECT pg_sleep(30)`);
await connection1.query(sql.typeAlias('void')`SELECT pg_sleep(30)`);
} catch (error) {
// This code will not be executed.
}
@@ -76,7 +76,7 @@ import {
let row;

try {
row = await connection.one(sql`SELECT foo`);
row = await connection.one(sql.typeAlias('foo')`SELECT foo`);
} catch (error) {
if (error instanceof DataIntegrityError) {
console.error('There is more than one row matching the select criteria.');
@@ -102,7 +102,7 @@ import {
let row;

try {
row = await connection.one(sql`SELECT foo`);
row = await connection.one(sql.typeAlias('foo')`SELECT foo`);
} catch (error) {
if (!(error instanceof NotFoundError)) {
throw error;
@@ -127,14 +127,14 @@ It should be safe to use the same connection if `StatementCancelledError` is han
```ts
await pool.connect(async (connection0) => {
await pool.connect(async (connection1) => {
const backendProcessId = await connection1.oneFirst(sql`SELECT pg_backend_pid()`);
const backendProcessId = await connection1.oneFirst(sql.typeAlias('id')`SELECT pg_backend_pid() AS id`);

setTimeout(() => {
connection0.query(sql`SELECT pg_cancel_backend(${backendProcessId})`)
connection0.query(sql.typeAlias('void')`SELECT pg_cancel_backend(${backendProcessId})`)
}, 2000);

try {
await connection1.query(sql`SELECT pg_sleep(30)`);
await connection1.query(sql.typeAlias('void')`SELECT pg_sleep(30)`);
} catch (error) {
if (error instanceof StatementCancelledError) {
// Safe to continue using the same connection.
Loading