diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2d23d1c9e..3e14cf20f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
- node-version: [18.x, 20.x, 22.x]
+ node-version: [18.x, 20.x, 22.4.1]
steps:
- uses: actions/checkout@v4
@@ -42,7 +42,7 @@ jobs:
strategy:
matrix:
- node-version: [18.x, 20.x, 22.x]
+ node-version: [18.x, 20.x, 22.4.1]
steps:
- uses: actions/checkout@v4
@@ -76,7 +76,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
- node-version: 22.x
+ node-version: 22.4.1
cache: 'npm'
- name: Install dependencies
@@ -107,7 +107,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
- node-version: 22.x
+ node-version: 22.4.1
cache: 'npm'
- name: Install dependencies
@@ -134,7 +134,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
- node-version: 22.x
+ node-version: 22.4.1
cache: 'npm'
- name: Install dependencies
@@ -156,7 +156,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
- node-version: 22.x
+ node-version: 22.4.1
cache: 'npm'
- name: Install dependencies
diff --git a/site/docs/examples/update/0010-single-row.js b/site/docs/examples/update/0010-single-row.js
index e82a23a65..b30a98077 100644
--- a/site/docs/examples/update/0010-single-row.js
+++ b/site/docs/examples/update/0010-single-row.js
@@ -5,6 +5,4 @@ export const singleRow = `const result = await db
last_name: 'Aniston'
})
.where('id', '=', '1')
- .executeTakeFirst()
-
-console.log(result.numUpdatedRows)`
\ No newline at end of file
+ .executeTakeFirst()`
\ No newline at end of file
diff --git a/site/docs/examples/update/0020-complex-values.js b/site/docs/examples/update/0020-complex-values.js
index 3b88fee1e..caad0ebb6 100644
--- a/site/docs/examples/update/0020-complex-values.js
+++ b/site/docs/examples/update/0020-complex-values.js
@@ -6,6 +6,4 @@ export const complexValues = `const result = await db
last_name: 'updated',
}))
.where('id', '=', '1')
- .executeTakeFirst()
-
-console.log(result.numUpdatedRows)`
\ No newline at end of file
+ .executeTakeFirst()`
\ No newline at end of file
diff --git a/site/docs/examples/update/0030-my-sql-joins.js b/site/docs/examples/update/0030-my-sql-joins.js
new file mode 100644
index 000000000..400b23c44
--- /dev/null
+++ b/site/docs/examples/update/0030-my-sql-joins.js
@@ -0,0 +1,7 @@
+export const mySqlJoins = `const result = await db
+ .updateTable(['person', 'pet'])
+ .set('person.first_name', 'Updated person')
+ .set('pet.name', 'Updated doggo')
+ .whereRef('person.id', '=', 'pet.owner_id')
+ .where('person.id', '=', '1')
+ .executeTakeFirst()`
\ No newline at end of file
diff --git a/site/docs/examples/update/0030-my-sql-joins.mdx b/site/docs/examples/update/0030-my-sql-joins.mdx
new file mode 100644
index 000000000..82c4db139
--- /dev/null
+++ b/site/docs/examples/update/0030-my-sql-joins.mdx
@@ -0,0 +1,40 @@
+---
+title: 'MySQL joins'
+---
+
+# MySQL joins
+
+MySQL allows you to join tables directly to the "main" table and update
+rows of all joined tables. This is possible by passing all tables to the
+`updateTable` method as a list and adding the `ON` conditions as `WHERE`
+statements. You can then use the `set(column, value)` variant to update
+columns using table qualified names.
+
+The `UpdateQueryBuilder` also has `innerJoin` etc. join methods, but those
+can only be used as part of a PostgreSQL `update set from join` query.
+Due to type complexity issues, we unfortunately can't make the same
+methods work in both cases.
+
+import {
+ Playground,
+ exampleSetup,
+} from '../../../src/components/Playground'
+
+import {
+ mySqlJoins
+} from './0030-my-sql-joins'
+
+
+
+:::info[More examples]
+The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/),
+but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always
+just one hover away!
+
+For example, check out these sections:
+ - [set method](https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#set)
+ - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#returning)
+ - [updateTable method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#updateTable)
+:::
diff --git a/site/docs/recipes/0001-reusable-helpers.md b/site/docs/recipes/0001-reusable-helpers.md
new file mode 100644
index 000000000..2cce66949
--- /dev/null
+++ b/site/docs/recipes/0001-reusable-helpers.md
@@ -0,0 +1,238 @@
+# Reusable helpers
+
+:::info
+[Here's](https://kyse.link/qm67s) a playground link containing all the code in this recipe.
+:::
+
+Let's say you want to write the following query:
+
+```sql
+SELECT id, first_name
+FROM person
+WHERE upper(last_name) = $1
+```
+
+Kysely doesn't have a built-in `upper` function but there are at least three ways you could write this:
+
+```ts
+const lastName = 'STALLONE'
+
+const persons = await db
+ .selectFrom('person')
+ .select(['id', 'first_name'])
+ // 1. `sql` template tag. This is the least type-safe option.
+ // You're providing the column name without any type-checking,
+ // and plugins won't affect it.
+ .where(
+ sql`upper(last_name)`, '=', lastName
+ )
+ // 2. `sql` template tag with `ref`. Anything passed to `ref`
+ // gets type-checked against the accumulated query context.
+ .where(({ eb, ref }) => eb(
+ sql`upper(${ref('last_name')})`, '=', lastName
+ ))
+ // 3. The `fn` function helps you avoid missing parentheses/commas
+ // errors and uses refs as 1st class arguments.
+ .where(({ eb, fn }) => eb(
+ fn('upper', ['last_name']), '=', lastName
+ ))
+ .execute()
+```
+
+but each option could be more readable or type-safe.
+
+Fortunately Kysely allows you to easily create composable, reusable and type-safe helper functions:
+
+```ts
+import { Expression, sql } from 'kysely'
+
+function upper(expr: Expression) {
+ return sql`upper(${expr})`
+}
+
+function lower(expr: Expression) {
+ return sql`lower(${expr})`
+}
+
+function concat(...exprs: Expression[]) {
+ return sql.join(exprs, sql`||`)
+}
+```
+
+Using the `upper` helper, our query would look like this:
+
+```ts
+const lastName = 'STALLONE'
+
+const persons = await db
+ .selectFrom('person')
+ .select(['id', 'first_name'])
+ .where(({ eb, ref }) => eb(
+ upper(ref('last_name')), '=', lastName
+ ))
+ .execute()
+```
+
+The recipe for helper functions is simple: take inputs as `Expression` instances where `T` is the type of the expression. For example `upper` takes in any `string` expression since it transforms strings to upper case. If you implemented the `round` function, it'd take in `Expression` since you can only round numbers.
+
+The helper functions should then use the inputs to create an output that's also an `Expression`. Everything you can create using the expression builder is an instance of `Expression`. So is the output of the `sql` template tag and all methods under the `sql` object. Same goes for `SelectQueryBuilder` and pretty much everything else in Kysely. Everything's an expression.
+
+See [this recipe](https://kysely.dev/docs/recipes/expressions) to learn more about expressions.
+
+So we've learned that everything's an expression and that expressions are composable. Let's put this idea to use:
+
+```ts
+const persons = await db
+ .selectFrom('person')
+ .select(['id', 'first_name'])
+ .where(({ eb, ref, val }) => eb(
+ concat(
+ lower(ref('first_name')),
+ val(' '),
+ upper(ref('last_name'))
+ ),
+ '=',
+ 'sylvester STALLONE'
+ ))
+ .execute()
+```
+
+So far we've only used our helper functions in the first argument of `where` but you can use them anywhere:
+
+```ts
+const persons = await db
+ .selectFrom('person')
+ .innerJoin('pet', (join) => join.on(eb => eb(
+ 'person.first_name', '=', lower(eb.ref('pet.name'))
+ )))
+ .select(({ ref, val }) => [
+ 'first_name',
+ // If you use a helper in `select`, you need to always provide an explicit
+ // name for it using the `as` method.
+ concat(ref('person.first_name'), val(' '), ref('pet.name')).as('name_with_pet')
+ ])
+ .orderBy(({ ref }) => lower(ref('first_name')))
+ .execute()
+```
+
+## Reusable helpers using `ExpressionBuilder`
+
+Here's an example of a helper function that uses the expression builder instead of raw SQL:
+
+```ts
+import { Expression, expressionBuilder } from 'kysely'
+
+function idsOfPersonsThatHaveDogNamed(name: Expression) {
+ const eb = expressionBuilder()
+
+ // A subquery that returns the identifiers of all persons
+ // that have a dog named `name`.
+ return eb
+ .selectFrom('pet')
+ .select('pet.owner_id')
+ .where('pet.species', '=', 'dog')
+ .where('pet.name', '=', name)
+}
+```
+
+And here's how you could use it:
+
+```ts
+const dogName = 'Doggo'
+
+const persons = await db
+ .selectFrom('person')
+ .selectAll('person')
+ .where((eb) => eb(
+ 'person.id', 'in', idsOfPersonsThatHaveDogNamed(eb.val(dogName))
+ ))
+ .execute()
+```
+
+Note that `idsOfPersonsThatHaveDogNamed` doesn't execute a separate query but instead returns a subquery expression that's compiled as a part of the parent query:
+
+```sql
+select
+ person.*
+from
+ person
+where
+ person.id in (
+ select pet.owner_id
+ from pet
+ where pet.species = 'dog'
+ and pet.name = ?
+ )
+```
+
+In all our examples we've used the following syntax:
+
+```ts
+.where(eb => eb(left, operator, right))
+```
+
+When the expression builder `eb` is used as a function, it creates a binary expression. All binary expressions with a comparison operator are represented as a `Expression`. You don't always need to return `eb(left, operator, right)` from the callback though. Since `Expressions` are composable and reusable, you can return any `Expression`.
+
+This means you can create helpers like this:
+
+```ts
+function isOlderThan(age: Expression) {
+ return sql`age > ${age}`
+}
+```
+
+```ts
+const persons = await db
+ .selectFrom('person')
+ .select(['id', 'first_name'])
+ .where(({ val }) => isOlderThan(val(60)))
+ .execute()
+```
+
+## Dealing with nullable expressions
+
+If you want your helpers to work with nullable expressions (nullable columns etc.), you can do something like this:
+
+```ts
+import { Expression } from 'kysely'
+
+// This function accepts both nullable and non-nullable string expressions.
+function toInt(expr: Expression) {
+ // This returns `Expression` if `expr` is nullable
+ // and `Expression` otherwise.
+ return sql`(${expr})::integer`
+}
+```
+
+## Passing select queries as expressions
+
+Let's say we have the following query:
+
+```ts
+const expr: Expression<{ name: string }> = db
+ .selectFrom('pet')
+ .select('pet.name')
+```
+
+The expression type of our query is `Expression<{ name: string }>` but SQL allows you to use a query like that as an `Expression`. In other words, SQL allows you to use single-column record types like scalars. Most of the time Kysely is able to automatically handle this case but with helper functions you need to use `$asScalar()` to convert the type. Here's an example:
+
+```ts
+const persons = await db
+ .selectFrom('person')
+ .select((eb) => [
+ 'id',
+ 'first_name',
+ upper(
+ eb.selectFrom('pet')
+ .select('name')
+ .whereRef('person.id', '=', 'pet.owner_id')
+ .limit(1)
+ .$asScalar() // <-- This is needed
+ .$notNull()
+ ).as('pet_name')
+ ])
+```
+
+The subquery is an `Expression<{ name: string }>` but our `upper` function only accepts `Expression`. That's why we need to call `$asScalar()`. `$asScalar()` has no effect on the generated SQL. It's simply a type-level helper.
+
+We also used `$notNull()` in the example because our simple `upper` function doesn't support nullable expressions.
\ No newline at end of file
diff --git a/site/docs/recipes/0006-expressions.md b/site/docs/recipes/0006-expressions.md
index 3ac86cead..154eb88a6 100644
--- a/site/docs/recipes/0006-expressions.md
+++ b/site/docs/recipes/0006-expressions.md
@@ -6,9 +6,9 @@ An [`Expression`](https://kysely-org.github.io/kysely-apidoc/interfaces/Expre
## Expression builder
-Expressions are usually built using an instance of [`ExpressionBuilder`](https://kysely-org.github.io/kysely-apidoc/interfaces/ExpressionBuilder.html). `DB` is the same database type you give to `Kysely` when you create an instance. `TB` is the union of all table names that are visible in the context. For example `ExpressionBuilder` means you can access `person` and `pet` tables and all their columns in the expression.
+Expressions are usually built using an instance of [`ExpressionBuilder`](https://kysely-org.github.io/kysely-apidoc/interfaces/ExpressionBuilder.html). `DB` is the same database type you give to `Kysely` when you create an instance. `TB` is the union of all table names that are visible in the context. For example `ExpressionBuilder` means you can reference `person` and `pet` columns in the created expressions.
-You can get an instance of the expression builder by using a callback:
+You can get an instance of the expression builder using a callback:
```ts
const person = await db
@@ -28,7 +28,13 @@ const person = await db
.as('pet_name'),
// Select a boolean expression
- eb('first_name', '=', 'Jennifer').as('is_jennifer')
+ eb('first_name', '=', 'Jennifer').as('is_jennifer'),
+
+ // Select a static string value
+ eb.val('Some value').as('string_value'),
+
+ // Select a literal value
+ eb.lit(42).as('literal_value'),
])
// You can also destructure the expression builder like this
.where(({ and, or, eb, not, exists, selectFrom }) => or([
@@ -63,19 +69,21 @@ select
limit 1
) as "pet_name",
- "first_name" = $1 as "is_jennifer"
+ "first_name" = $1 as "is_jennifer",
+ $2 as "string_value",
+ 42 as "literal_value"
from
"person"
where (
(
- "first_name" = $2
- and "last_name" = $3
+ "first_name" = $3
+ and "last_name" = $4
)
or not exists (
select "pet.id"
from "pet"
where "pet"."owner_id" = "person"."id"
- and "pet"."species" in ($4, $5)
+ and "pet"."species" in ($5, $6)
)
)
```
@@ -91,11 +99,17 @@ There's also a global function `expressionBuilder` you can use to create express
```ts
import { expressionBuilder } from 'kysely'
-// `eb1` has type `ExpressionBuilder`
-const eb1 = expressionBuilder()
+// `eb1` has type `ExpressionBuilder` which means there are no tables in the
+// context. This variant should be used most of the time in helper functions since you
+// shouldn't make assumptions about the calling context.
+const eb1 = expressionBuilder()
+
+// `eb2` has type `ExpressionBuilder`. You can reference `person` columns
+// directly in all expression builder methods.
+const eb2 = expressionBuilder()
// In this one you'd have access to tables `person` and `pet` and all their columns.
-const eb2 = expressionBuilder()
+const eb3 = expressionBuilder()
let qb = query
.selectFrom('person')
@@ -141,7 +155,7 @@ const doggoPersons = await db
.execute()
```
-The above helper is not very type-safe. The following code would compile, but fail at runtime:
+However, the above helper is not very type-safe. The following code would compile, but fail at runtime:
```ts
const bigFatFailure = await db
@@ -160,7 +174,7 @@ in arbitrary expressions instead of just values.
function hasDogNamed(name: Expression, ownerId: Expression) {
// Create an expression builder without any tables in the context.
// This way we make no assumptions about the calling context.
- const eb = expressionBuilder()
+ const eb = expressionBuilder()
return eb.exists(
eb.selectFrom('pet')
@@ -182,11 +196,13 @@ const doggoPersons = await db
.execute()
```
+Learn more about reusable helper functions [here](https://kysely.dev/docs/recipes/reusable-helpers).
+
## Conditional expressions
In the following, we'll only cover `where` expressions. The same logic applies to `having`, `on`, `orderBy`, `groupBy` etc.
-> This section should not be confused with conditional selections in `select` clauses, which is a whole 'nother topic we discuss in [this recipe](https://www.kysely.dev/docs/recipes/conditional-selects).
+> This section should not be confused with conditional selections in `select` clauses, which is a whole 'nother topic we discuss in [this recipe](https://kysely.dev/docs/recipes/conditional-selects).
Having a set of optional filters you want to combine using `and`, is the most basic and common use case of conditional `where` expressions.
Since the `where`, `having` and other filter functions are additive, most of the time this is enough:
diff --git a/site/package-lock.json b/site/package-lock.json
index 530031d2e..c6e6ab2ee 100644
--- a/site/package-lock.json
+++ b/site/package-lock.json
@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "kysely",
- "version": "0.27.0",
+ "version": "0.27.3",
"dependencies": {
"@docusaurus/core": "^3.4.0",
"@docusaurus/preset-classic": "^3.4.0",
diff --git a/src/dialect/postgres/postgres-dialect-config.ts b/src/dialect/postgres/postgres-dialect-config.ts
index 02cba7c09..497b756b3 100644
--- a/src/dialect/postgres/postgres-dialect-config.ts
+++ b/src/dialect/postgres/postgres-dialect-config.ts
@@ -52,30 +52,54 @@ export interface PostgresPool {
}
export interface PostgresPoolClient {
- query(
- sql: string,
- parameters: ReadonlyArray,
- ): Promise>
+ query(queryConfig: PostgresQueryConfig): Promise>
query(cursor: PostgresCursor): PostgresCursor
release(): void
}
+// https://node-postgres.com/apis/client#queryconfig
+export interface PostgresQueryConfig {
+ text: string
+ values: ReadonlyArray
+ name?: string
+ rowMode: 'array'
+ types?: any
+ queryMode?: string
+}
+
export interface PostgresCursor {
- read(rowsCount: number): Promise
+ // https://github.com/brianc/node-pg-cursor/pull/23
+ read(
+ rowCount: number,
+ cb: (err: Error | null, rows: T[], result?: PostgresQueryResult) => void,
+ ): void
close(): Promise
}
export type PostgresCursorConstructor = new (
sql: string,
parameters: unknown[],
+ queryConfig?: { rowMode?: 'array' },
) => PostgresCursor
export interface PostgresQueryResult {
command: 'UPDATE' | 'DELETE' | 'INSERT' | 'SELECT' | 'MERGE'
rowCount: number
rows: R[]
+ fields: PostgresField[]
+ rowAsArray: boolean
}
export interface PostgresStream {
[Symbol.asyncIterator](): AsyncIterableIterator
}
+
+export interface PostgresField {
+ name: keyof R
+ tableID: number
+ columnID: number
+ dataTypeID: number
+ dataTypeSize: number
+ dataTypeModifier: number
+ format: string
+}
diff --git a/src/dialect/postgres/postgres-driver.ts b/src/dialect/postgres/postgres-driver.ts
index 7b6639fe2..ecef69659 100644
--- a/src/dialect/postgres/postgres-driver.ts
+++ b/src/dialect/postgres/postgres-driver.ts
@@ -3,14 +3,17 @@ import {
QueryResult,
} from '../../driver/database-connection.js'
import { Driver, TransactionSettings } from '../../driver/driver.js'
+import { SelectQueryNode } from '../../operation-node/select-query-node.js'
import { CompiledQuery } from '../../query-compiler/compiled-query.js'
import { isFunction, freeze } from '../../util/object-utils.js'
import { extendStackTrace } from '../../util/stack-trace-utils.js'
import {
PostgresCursorConstructor,
PostgresDialectConfig,
+ PostgresField,
PostgresPool,
PostgresPoolClient,
+ PostgresQueryResult,
} from './postgres-dialect-config.js'
const PRIVATE_RELEASE_METHOD = Symbol()
@@ -106,9 +109,13 @@ class PostgresConnection implements DatabaseConnection {
async executeQuery(compiledQuery: CompiledQuery): Promise> {
try {
- const result = await this.#client.query(compiledQuery.sql, [
- ...compiledQuery.parameters,
- ])
+ const result = await this.#client.query({
+ text: compiledQuery.sql,
+ values: [...compiledQuery.parameters],
+ rowMode: 'array',
+ })
+
+ const rows = this.#processRows(result)
if (
result.command === 'INSERT' ||
@@ -122,12 +129,12 @@ class PostgresConnection implements DatabaseConnection {
// TODO: remove.
numUpdatedOrDeletedRows: numAffectedRows,
numAffectedRows,
- rows: result.rows ?? [],
+ rows,
}
}
return {
- rows: result.rows ?? [],
+ rows,
}
} catch (err) {
throw extendStackTrace(err, new Error())
@@ -152,12 +159,24 @@ class PostgresConnection implements DatabaseConnection {
new this.#options.cursor(
compiledQuery.sql,
compiledQuery.parameters.slice(),
+ { rowMode: 'array' },
),
)
try {
while (true) {
- const rows = await cursor.read(chunkSize)
+ const result = await new Promise | undefined>(
+ (resolve, reject) =>
+ cursor.read(chunkSize, (err, _rows, result) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve(result)
+ }
+ }),
+ )
+
+ const rows = this.#processRows(result)
if (rows.length === 0) {
break
@@ -172,6 +191,38 @@ class PostgresConnection implements DatabaseConnection {
}
}
+ #processRows(
+ result:
+ | Pick, 'fields' | 'rows' | 'rowAsArray'>
+ | undefined,
+ ): R[] {
+ const { fields, rows, rowAsArray } = result || {}
+
+ if (!rows?.length) {
+ return []
+ }
+
+ if (!rowAsArray) {
+ return rows as R[]
+ }
+
+ const processedRows: R[] = []
+
+ for (let i = 0, len = rows.length; i < len; i++) {
+ const row = rows[i] as unknown[]
+ const processedRow = {} as R
+
+ for (let j = 0, len = row.length; j < len; j++) {
+ const column = fields![j].name
+ processedRow[column] = row[j] as R[keyof R]
+ }
+
+ processedRows.push(processedRow)
+ }
+
+ return processedRows
+ }
+
[PRIVATE_RELEASE_METHOD](): void {
this.#client.release()
}
diff --git a/src/expression/expression-builder.ts b/src/expression/expression-builder.ts
index b22277545..938040882 100644
--- a/src/expression/expression-builder.ts
+++ b/src/expression/expression-builder.ts
@@ -5,13 +5,7 @@ import {
import { SelectQueryNode } from '../operation-node/select-query-node.js'
import {
parseTableExpressionOrList,
- TableExpression,
- From,
TableExpressionOrList,
- FromTables,
- ExtractTableAlias,
- AnyAliasedTable,
- PickTableWithAlias,
parseTable,
} from '../parser/table-parser.js'
import { WithSchemaPlugin } from '../plugin/with-schema/with-schema-plugin.js'
@@ -83,6 +77,7 @@ import {
parseDataTypeExpression,
} from '../parser/data-type-parser.js'
import { CastNode } from '../operation-node/cast-node.js'
+import { SelectFrom } from '../parser/select-from-parser.js'
export interface ExpressionBuilder {
/**
@@ -151,7 +146,7 @@ export interface ExpressionBuilder {
* eb.selectFrom('person')
* .selectAll()
* .where((eb) => eb(
- * eb.fn('lower', ['first_name']),
+ * eb.fn('lower', ['first_name']),
* 'in',
* eb.selectFrom('pet')
* .select('pet.name')
@@ -182,12 +177,12 @@ export interface ExpressionBuilder {
*
* ```ts
* db.selectFrom('person')
+ * .selectAll()
* .where(({ eb, exists, selectFrom }) =>
- * eb('first_name', '=', 'Jennifer').and(
- * exists(selectFrom('pet').whereRef('owner_id', '=', 'person.id').select('pet.id'))
- * )
+ * eb('first_name', '=', 'Jennifer').and(exists(
+ * selectFrom('pet').whereRef('owner_id', '=', 'person.id').select('pet.id')
+ * ))
* )
- * .selectAll()
* ```
*
* The generated SQL (PostgreSQL):
@@ -277,29 +272,9 @@ export interface ExpressionBuilder {
* that case Kysely typings wouldn't allow you to reference `pet.owner_id`
* because `pet` is not joined to that query.
*/
- selectFrom(
- from: TE[],
- ): SelectQueryBuilder, {}>
-
- selectFrom>(
- from: TE[],
- ): SelectQueryBuilder, FromTables, {}>
-
- selectFrom(
- from: TE,
- ): SelectQueryBuilder, {}>
-
- selectFrom>(
- from: TE,
- ): SelectQueryBuilder<
- DB & PickTableWithAlias,
- TB | ExtractTableAlias, TE>,
- {}
- >
-
- selectFrom>(
+ selectFrom>(
from: TE,
- ): SelectQueryBuilder, FromTables, {}>
+ ): SelectFrom
/**
* Creates a `case` statement/operator.
@@ -1311,10 +1286,10 @@ export function expressionBuilder(
_: SelectQueryBuilder,
): ExpressionBuilder
-export function expressionBuilder(): ExpressionBuilder<
+export function expressionBuilder<
DB,
- TB
->
+ TB extends keyof DB = never,
+>(): ExpressionBuilder
export function expressionBuilder(
_?: unknown,
diff --git a/src/index.ts b/src/index.ts
index 9818f1131..b467d871c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -53,6 +53,7 @@ export * from './schema/column-definition-builder.js'
export * from './schema/foreign-key-constraint-builder.js'
export * from './schema/alter-table-builder.js'
export * from './schema/create-view-builder.js'
+export * from './schema/refresh-materialized-view-builder.js'
export * from './schema/drop-view-builder.js'
export * from './schema/alter-column-builder.js'
@@ -133,6 +134,7 @@ export * from './operation-node/create-schema-node.js'
export * from './operation-node/create-table-node.js'
export * from './operation-node/create-type-node.js'
export * from './operation-node/create-view-node.js'
+export * from './operation-node/refresh-materialized-view-node.js'
export * from './operation-node/data-type-node.js'
export * from './operation-node/default-insert-value-node.js'
export * from './operation-node/default-value-node.js'
diff --git a/src/operation-node/data-type-node.ts b/src/operation-node/data-type-node.ts
index 2d88f43d4..acd04c8aa 100644
--- a/src/operation-node/data-type-node.ts
+++ b/src/operation-node/data-type-node.ts
@@ -33,6 +33,18 @@ const SIMPLE_COLUMN_DATA_TYPES = [
'jsonb',
'blob',
'varbinary',
+ 'int4range',
+ 'int4multirange',
+ 'int8range',
+ 'int8multirange',
+ 'numrange',
+ 'nummultirange',
+ 'tsrange',
+ 'tsmultirange',
+ 'tstzrange',
+ 'tstzmultirange',
+ 'daterange',
+ 'datemultirange',
] as const
const COLUMN_DATA_TYPE_REGEX = [
diff --git a/src/operation-node/operation-node-transformer.ts b/src/operation-node/operation-node-transformer.ts
index 0d01b6bba..2f44df171 100644
--- a/src/operation-node/operation-node-transformer.ts
+++ b/src/operation-node/operation-node-transformer.ts
@@ -94,6 +94,7 @@ import { CastNode } from './cast-node.js'
import { FetchNode } from './fetch-node.js'
import { TopNode } from './top-node.js'
import { OutputNode } from './output-node.js'
+import { RefreshMaterializedViewNode } from './refresh-materialized-view-node.js'
/**
* Transforms an operation node tree into another one.
@@ -189,6 +190,7 @@ export class OperationNodeTransformer {
DropConstraintNode: this.transformDropConstraint.bind(this),
ForeignKeyConstraintNode: this.transformForeignKeyConstraint.bind(this),
CreateViewNode: this.transformCreateView.bind(this),
+ RefreshMaterializedViewNode: this.transformRefreshMaterializedView.bind(this),
DropViewNode: this.transformDropView.bind(this),
GeneratedNode: this.transformGenerated.bind(this),
DefaultValueNode: this.transformDefaultValue.bind(this),
@@ -796,6 +798,15 @@ export class OperationNodeTransformer {
})
}
+ protected transformRefreshMaterializedView(node: RefreshMaterializedViewNode): RefreshMaterializedViewNode {
+ return requireAllProps({
+ kind: 'RefreshMaterializedViewNode',
+ name: this.transformNode(node.name),
+ concurrently: node.concurrently,
+ withNoData: node.withNoData,
+ })
+ }
+
protected transformDropView(node: DropViewNode): DropViewNode {
return requireAllProps({
kind: 'DropViewNode',
diff --git a/src/operation-node/operation-node-visitor.ts b/src/operation-node/operation-node-visitor.ts
index f88f12539..bbe02da53 100644
--- a/src/operation-node/operation-node-visitor.ts
+++ b/src/operation-node/operation-node-visitor.ts
@@ -96,6 +96,7 @@ import { CastNode } from './cast-node.js'
import { FetchNode } from './fetch-node.js'
import { TopNode } from './top-node.js'
import { OutputNode } from './output-node.js'
+import { RefreshMaterializedViewNode } from './refresh-materialized-view-node.js'
export abstract class OperationNodeVisitor {
protected readonly nodeStack: OperationNode[] = []
@@ -166,6 +167,7 @@ export abstract class OperationNodeVisitor {
DropConstraintNode: this.visitDropConstraint.bind(this),
ForeignKeyConstraintNode: this.visitForeignKeyConstraint.bind(this),
CreateViewNode: this.visitCreateView.bind(this),
+ RefreshMaterializedViewNode: this.visitRefreshMaterializedView.bind(this),
DropViewNode: this.visitDropView.bind(this),
GeneratedNode: this.visitGenerated.bind(this),
DefaultValueNode: this.visitDefaultValue.bind(this),
@@ -277,6 +279,7 @@ export abstract class OperationNodeVisitor {
protected abstract visitPrimitiveValueList(node: PrimitiveValueListNode): void
protected abstract visitOperator(node: OperatorNode): void
protected abstract visitCreateView(node: CreateViewNode): void
+ protected abstract visitRefreshMaterializedView(node: RefreshMaterializedViewNode): void
protected abstract visitDropView(node: DropViewNode): void
protected abstract visitGenerated(node: GeneratedNode): void
protected abstract visitDefaultValue(node: DefaultValueNode): void
diff --git a/src/operation-node/operation-node.ts b/src/operation-node/operation-node.ts
index 6476a24eb..7786b7ec1 100644
--- a/src/operation-node/operation-node.ts
+++ b/src/operation-node/operation-node.ts
@@ -58,6 +58,7 @@ export type OperationNodeKind =
| 'AddConstraintNode'
| 'DropConstraintNode'
| 'CreateViewNode'
+ | 'RefreshMaterializedViewNode'
| 'DropViewNode'
| 'GeneratedNode'
| 'DefaultValueNode'
diff --git a/src/operation-node/refresh-materialized-view-node.ts b/src/operation-node/refresh-materialized-view-node.ts
new file mode 100644
index 000000000..e421a087b
--- /dev/null
+++ b/src/operation-node/refresh-materialized-view-node.ts
@@ -0,0 +1,41 @@
+import { freeze } from '../util/object-utils.js'
+import { OperationNode } from './operation-node.js'
+import { SchemableIdentifierNode } from './schemable-identifier-node.js'
+
+export type RefreshMaterializedViewNodeParams = Omit<
+ Partial,
+ 'kind' | 'name'
+>
+
+export interface RefreshMaterializedViewNode extends OperationNode {
+ readonly kind: 'RefreshMaterializedViewNode'
+ readonly name: SchemableIdentifierNode
+ readonly concurrently?: boolean
+ readonly withNoData?: boolean
+}
+
+/**
+ * @internal
+ */
+export const RefreshMaterializedViewNode = freeze({
+ is(node: OperationNode): node is RefreshMaterializedViewNode {
+ return node.kind === 'RefreshMaterializedViewNode'
+ },
+
+ create(name: string): RefreshMaterializedViewNode {
+ return freeze({
+ kind: 'RefreshMaterializedViewNode',
+ name: SchemableIdentifierNode.create(name),
+ })
+ },
+
+ cloneWith(
+ createView: RefreshMaterializedViewNode,
+ params: RefreshMaterializedViewNodeParams,
+ ): RefreshMaterializedViewNode {
+ return freeze({
+ ...createView,
+ ...params,
+ })
+ },
+})
diff --git a/src/operation-node/update-query-node.ts b/src/operation-node/update-query-node.ts
index c4188205a..a815a45c9 100644
--- a/src/operation-node/update-query-node.ts
+++ b/src/operation-node/update-query-node.ts
@@ -12,6 +12,7 @@ import { ExplainNode } from './explain-node.js'
import { LimitNode } from './limit-node.js'
import { TopNode } from './top-node.js'
import { OutputNode } from './output-node.js'
+import { ListNode } from './list-node.js'
export type UpdateValuesNode = ValueListNode | PrimitiveValueListNode
@@ -39,10 +40,15 @@ export const UpdateQueryNode = freeze({
return node.kind === 'UpdateQueryNode'
},
- create(table: OperationNode, withNode?: WithNode): UpdateQueryNode {
+ create(
+ tables: ReadonlyArray,
+ withNode?: WithNode,
+ ): UpdateQueryNode {
return freeze({
kind: 'UpdateQueryNode',
- table,
+ // For backwards compatibility, use the raw table node when there's only one table
+ // and don't rename the property to something like `tables`.
+ table: tables.length === 1 ? tables[0] : ListNode.create(tables),
...(withNode && { with: withNode }),
})
},
diff --git a/src/parser/delete-from-parser.ts b/src/parser/delete-from-parser.ts
new file mode 100644
index 000000000..f46143277
--- /dev/null
+++ b/src/parser/delete-from-parser.ts
@@ -0,0 +1,30 @@
+import type { DeleteQueryBuilder } from '../query-builder/delete-query-builder.js'
+import type { DeleteResult } from '../query-builder/delete-result.js'
+import type { ShallowRecord } from '../util/type-utils.js'
+import type {
+ ExtractTableAlias,
+ From,
+ FromTables,
+ TableExpressionOrList,
+} from './table-parser.js'
+
+export type DeleteFrom> =
+ TE extends ReadonlyArray
+ ? DeleteQueryBuilder, FromTables, DeleteResult>
+ : TE extends keyof DB & string
+ ? // This branch creates a good-looking type for the most common case:
+ // deleteFrom('person') --> DeleteQueryBuilder.
+ // ExtractTableAlias is needed for the case where DB == any. Without it:
+ // deleteFrom('person as p') --> DeleteQueryBuilder
+ DeleteQueryBuilder, DeleteResult>
+ : // This branch creates a good-looking type for common aliased case:
+ // deleteFrom('person as p') --> DeleteQueryBuilder.
+ TE extends `${infer T} as ${infer A}`
+ ? T extends keyof DB
+ ? DeleteQueryBuilder, A, DeleteResult>
+ : never
+ : DeleteQueryBuilder<
+ From,
+ FromTables,
+ DeleteResult
+ >
diff --git a/src/parser/merge-into-parser.ts b/src/parser/merge-into-parser.ts
new file mode 100644
index 000000000..3a4df1fd3
--- /dev/null
+++ b/src/parser/merge-into-parser.ts
@@ -0,0 +1,15 @@
+import type { MergeQueryBuilder } from '../query-builder/merge-query-builder.js'
+import type { MergeResult } from '../query-builder/merge-result.js'
+import type { ShallowRecord } from '../util/type-utils.js'
+import type { ExtractTableAlias, SimpleTableReference } from './table-parser.js'
+
+export type MergeInto<
+ DB,
+ TE extends SimpleTableReference,
+> = TE extends keyof DB & string
+ ? MergeQueryBuilder, MergeResult>
+ : TE extends `${infer T} as ${infer A}`
+ ? T extends keyof DB
+ ? MergeQueryBuilder, A, MergeResult>
+ : never
+ : never
diff --git a/src/parser/returning-parser.ts b/src/parser/returning-parser.ts
index dac0eee72..0980ada94 100644
--- a/src/parser/returning-parser.ts
+++ b/src/parser/returning-parser.ts
@@ -26,4 +26,4 @@ export type ReturningAllRow = O extends
| UpdateResult
| MergeResult
? AllSelection
- : O & AllSelection
+ : AllSelection
diff --git a/src/parser/select-from-parser.ts b/src/parser/select-from-parser.ts
new file mode 100644
index 000000000..521daafd5
--- /dev/null
+++ b/src/parser/select-from-parser.ts
@@ -0,0 +1,29 @@
+import type { SelectQueryBuilder } from '../query-builder/select-query-builder.js'
+import type { ShallowRecord } from '../util/type-utils.js'
+import type {
+ ExtractTableAlias,
+ From,
+ FromTables,
+ TableExpressionOrList,
+} from './table-parser.js'
+
+export type SelectFrom<
+ DB,
+ TB extends keyof DB,
+ TE extends TableExpressionOrList,
+> =
+ TE extends ReadonlyArray
+ ? SelectQueryBuilder, FromTables, {}>
+ : TE extends keyof DB & string
+ ? // This branch creates a good-looking type for the most common case:
+ // selectFrom('person') --> SelectQueryBuilder.
+ // ExtractTableAlias is needed for the case where DB == any. Without it:
+ // selectFrom('person as p') --> SelectQueryBuilder
+ SelectQueryBuilder, {}>
+ : // This branch creates a good-looking type for common aliased case:
+ // selectFrom('person as p') --> SelectQueryBuilder.
+ TE extends `${infer T} as ${infer A}`
+ ? T extends keyof DB
+ ? SelectQueryBuilder, TB | A, {}>
+ : never
+ : SelectQueryBuilder, FromTables, {}>
diff --git a/src/parser/select-parser.ts b/src/parser/select-parser.ts
index f39fbbc44..50a022953 100644
--- a/src/parser/select-parser.ts
+++ b/src/parser/select-parser.ts
@@ -73,9 +73,10 @@ export type SelectArg<
| ReadonlyArray
| ((eb: ExpressionBuilder) => ReadonlyArray)
-type FlattenSelectExpression = SE extends DynamicReferenceBuilder
- ? { [R in RA]: DynamicReferenceBuilder }[RA]
- : SE
+type FlattenSelectExpression =
+ SE extends DynamicReferenceBuilder
+ ? { [R in RA]: DynamicReferenceBuilder }[RA]
+ : SE
type ExtractAliasFromSelectExpression = SE extends string
? ExtractAliasFromStringSelectExpression
@@ -154,10 +155,14 @@ type ExtractTypeFromStringSelectExpression<
? ExtractColumnType
: never
-export type AllSelection = DrainOuterGeneric<{
- [C in AnyColumn]: {
- [T in TB]: SelectType
- }[TB]
+export type AllSelection = DrainOuterGeneric<{
+ [C in AnyColumn | keyof O]: C extends AnyColumn
+ ? {
+ [T in TB]: SelectType
+ }[TB]
+ : C extends keyof O
+ ? O[C]
+ : never
}>
export function parseSelectArg(
diff --git a/src/parser/table-parser.ts b/src/parser/table-parser.ts
index 9c665c16e..d9e9fed03 100644
--- a/src/parser/table-parser.ts
+++ b/src/parser/table-parser.ts
@@ -8,7 +8,7 @@ import {
import { IdentifierNode } from '../operation-node/identifier-node.js'
import { OperationNode } from '../operation-node/operation-node.js'
import { AliasedExpression } from '../expression/expression.js'
-import { DrainOuterGeneric, ShallowRecord } from '../util/type-utils.js'
+import { DrainOuterGeneric } from '../util/type-utils.js'
export type TableExpression =
| AnyAliasedTable
@@ -19,17 +19,9 @@ export type TableExpressionOrList =
| TableExpression
| ReadonlyArray>
-export type TableReference =
- | SimpleTableReference
- | AliasedExpression
-
export type SimpleTableReference = AnyAliasedTable | AnyTable
-
export type AnyAliasedTable = `${AnyTable} as ${string}`
-
-export type TableReferenceOrList =
- | TableReference
- | ReadonlyArray>
+export type AnyTable = keyof DB & string
export type From = DrainOuterGeneric<{
[C in
@@ -56,15 +48,6 @@ export type ExtractTableAlias = TE extends `${string} as ${infer TA}`
? TE
: never
-export type PickTableWithAlias<
- DB,
- T extends AnyAliasedTable,
-> = T extends `${infer TB} as ${infer A}`
- ? TB extends keyof DB
- ? ShallowRecord
- : never
- : never
-
type ExtractAliasFromTableExpression = TE extends string
? TE extends `${string} as ${infer TA}`
? TA
@@ -101,8 +84,6 @@ type ExtractRowTypeFromTableExpression<
: never
: never
-type AnyTable = keyof DB & string
-
export function parseTableExpressionOrList(
table: TableExpressionOrList,
): OperationNode[] {
diff --git a/src/parser/update-parser.ts b/src/parser/update-parser.ts
new file mode 100644
index 000000000..95f4a0863
--- /dev/null
+++ b/src/parser/update-parser.ts
@@ -0,0 +1,41 @@
+import type { UpdateQueryBuilder } from '../query-builder/update-query-builder.js'
+import type { UpdateResult } from '../query-builder/update-result.js'
+import type { ShallowRecord } from '../util/type-utils.js'
+import type {
+ ExtractTableAlias,
+ From,
+ FromTables,
+ TableExpressionOrList,
+} from './table-parser.js'
+
+export type UpdateTable> =
+ TE extends ReadonlyArray
+ ? UpdateQueryBuilder<
+ From,
+ FromTables,
+ FromTables,
+ UpdateResult
+ >
+ : TE extends keyof DB & string
+ ? // This branch creates a good-looking type for the most common case:
+ // updateTable('person') --> UpdateQueryBuilder.
+ // ExtractTableAlias is needed for the case where DB == any. Without it:
+ // updateTable('person as p') --> UpdateQueryBuilder
+ UpdateQueryBuilder<
+ DB,
+ ExtractTableAlias,
+ ExtractTableAlias,
+ UpdateResult
+ >
+ : // This branch creates a good-looking type for common aliased case:
+ // updateTable('person as p') --> UpdateQueryBuilder.
+ TE extends `${infer T} as ${infer A}`
+ ? T extends keyof DB
+ ? UpdateQueryBuilder, A, A, UpdateResult>
+ : never
+ : UpdateQueryBuilder<
+ From,
+ FromTables,
+ FromTables,
+ UpdateResult
+ >
diff --git a/src/parser/update-set-parser.ts b/src/parser/update-set-parser.ts
index c3ce3fe4c..248491956 100644
--- a/src/parser/update-set-parser.ts
+++ b/src/parser/update-set-parser.ts
@@ -12,12 +12,19 @@ import {
parseReferenceExpression,
ReferenceExpression,
} from './reference-parser.js'
+import { AnyColumn, DrainOuterGeneric } from '../util/type-utils.js'
-export type UpdateObject = {
- [C in UpdateKeys]?:
- | ValueExpression>
- | undefined
-}
+export type UpdateObject<
+ DB,
+ TB extends keyof DB,
+ UT extends keyof DB = TB,
+> = DrainOuterGeneric<{
+ [C in AnyColumn]?: {
+ [T in UT]: C extends keyof DB[T]
+ ? ValueExpression> | undefined
+ : never
+ }[UT]
+}>
export type UpdateObjectFactory<
DB,
diff --git a/src/plugin/with-schema/with-schema-transformer.ts b/src/plugin/with-schema/with-schema-transformer.ts
index af14b0b3d..3fd15a0e1 100644
--- a/src/plugin/with-schema/with-schema-transformer.ts
+++ b/src/plugin/with-schema/with-schema-transformer.ts
@@ -1,5 +1,6 @@
import { AliasNode } from '../../operation-node/alias-node.js'
import { IdentifierNode } from '../../operation-node/identifier-node.js'
+import { ListNode } from '../../operation-node/list-node.js'
import { OperationNodeTransformer } from '../../operation-node/operation-node-transformer.js'
import { OperationNode } from '../../operation-node/operation-node.js'
import { ReferencesNode } from '../../operation-node/references-node.js'
@@ -21,6 +22,7 @@ const ROOT_OPERATION_NODES: Record = freeze({
CreateTableNode: true,
CreateTypeNode: true,
CreateViewNode: true,
+ RefreshMaterializedViewNode: true,
DeleteQueryNode: true,
DropIndexNode: true,
DropSchemaNode: true,
@@ -157,14 +159,14 @@ export class WithSchemaTransformer extends OperationNodeTransformer {
node: OperationNode,
schemableIds: Set,
): void {
- const table = TableNode.is(node)
- ? node
- : AliasNode.is(node) && TableNode.is(node.node)
- ? node.node
- : null
-
- if (table) {
- this.#collectSchemableId(table.table, schemableIds)
+ if (TableNode.is(node)) {
+ this.#collectSchemableId(node.table, schemableIds)
+ } else if (AliasNode.is(node) && TableNode.is(node.node)) {
+ this.#collectSchemableId(node.node.table, schemableIds)
+ } else if (ListNode.is(node)) {
+ for (const table of node.items) {
+ this.#collectSchemableIdsFromTableExpr(table, schemableIds)
+ }
}
}
diff --git a/src/query-builder/select-query-builder.ts b/src/query-builder/select-query-builder.ts
index 34c006654..a5a8d3d4e 100644
--- a/src/query-builder/select-query-builder.ts
+++ b/src/query-builder/select-query-builder.ts
@@ -243,7 +243,7 @@ export interface SelectQueryBuilder
* import { sql } from 'kysely'
*
* const persons = await db.selectFrom('person')
- * .select(({ eb, selectFrom, or }) => [
+ * .select(({ eb, selectFrom, or, val, lit }) => [
* // Select a correlated subquery
* selectFrom('pet')
* .whereRef('person.id', '=', 'pet.owner_id')
@@ -260,7 +260,13 @@ export interface SelectQueryBuilder
* ]).as('is_jennifer_or_arnold'),
*
* // Select a raw sql expression
- * sql`concat(first_name, ' ', last_name)`.as('full_name')
+ * sql`concat(first_name, ' ', last_name)`.as('full_name').
+ *
+ * // Select a static string value
+ * val('Some value').as('string_value'),
+ *
+ * // Select a literal value
+ * lit(42).as('literal_value'),
* ])
* .execute()
* ```
@@ -277,7 +283,9 @@ export interface SelectQueryBuilder
* limit $1
* ) as "pet_name",
* ("first_name" = $2 or "first_name" = $3) as "jennifer_or_arnold",
- * concat(first_name, ' ', last_name) as "full_name"
+ * concat(first_name, ' ', last_name) as "full_name",
+ * $4 as "string_value",
+ * 42 as "literal_value"
* from "person"
* ```
*
@@ -559,13 +567,13 @@ export interface SelectQueryBuilder
*/
selectAll(
table: ReadonlyArray,
- ): SelectQueryBuilder>
+ ): SelectQueryBuilder>
selectAll(
table: T,
- ): SelectQueryBuilder>
+ ): SelectQueryBuilder>
- selectAll(): SelectQueryBuilder>
+ selectAll(): SelectQueryBuilder>
/**
* Joins another table to the query using an inner join.
@@ -1716,6 +1724,54 @@ export interface SelectQueryBuilder
? ExpressionWrapper
: KyselyTypeError<'$asTuple() call failed: All selected columns must be provided as arguments'>
+ /**
+ * Plucks the value type of the output record.
+ *
+ * In SQL, any record type that only has one column can be used as a scalar.
+ * For example a query like this works:
+ *
+ * ```sql
+ * select
+ * id,
+ * first_name
+ * from
+ * person as p
+ * where
+ * -- This is ok since the query only selects one row
+ * -- and one column.
+ * (select name from pet where pet.owner_id = p.id limit 1) = 'Doggo'
+ * ```
+ *
+ * In many cases Kysely handles this automatically and picks the correct
+ * scalar type instead of the record type, but sometimes you need to give
+ * Kysely a hint.
+ *
+ * One such case are custom helper functions that take `Expression`
+ * instances as inputs:
+ *
+ * ```ts
+ * function doStuff(expr: Expression) {
+ * ...
+ * }
+ *
+ * // Error! This is not ok because the expression type is
+ * // `{ first_name: string }` instead of `string`.
+ * doStuff(db.selectFrom('person').select('first_name'))
+ *
+ * // Ok! This is ok since we've plucked the `string` type of the
+ * // only column in the output type.
+ * doStuff(db.selectFrom('person').select('first_name').$asScalar())
+ * ```
+ *
+ * This function has absolutely no effect on the generated SQL. It's
+ * purely a type-level helper.
+ *
+ * This method returns an `ExpressionWrapper` instead of a `SelectQueryBuilder`
+ * since the return value should only be used as a part of an expression
+ * and never executed as the main query.
+ */
+ $asScalar(): ExpressionWrapper
+
/**
* Narrows (parts of) the output type of the query.
*
@@ -2341,6 +2397,10 @@ class SelectQueryBuilderImpl
return new ExpressionWrapper(this.toOperationNode())
}
+ $asScalar(): ExpressionWrapper {
+ return new ExpressionWrapper(this.toOperationNode())
+ }
+
withPlugin(plugin: KyselyPlugin): SelectQueryBuilder {
return new SelectQueryBuilderImpl({
...this.#props,
@@ -2504,12 +2564,12 @@ export type SelectQueryBuilderWithInnerJoin<
? InnerJoinedBuilder
: never
: TE extends keyof DB
- ? SelectQueryBuilder
- : TE extends AliasedExpression
- ? InnerJoinedBuilder
- : TE extends (qb: any) => AliasedExpression
- ? InnerJoinedBuilder
- : never
+ ? SelectQueryBuilder
+ : TE extends AliasedExpression
+ ? InnerJoinedBuilder
+ : TE extends (qb: any) => AliasedExpression
+ ? InnerJoinedBuilder
+ : never
type InnerJoinedBuilder<
DB,
@@ -2536,12 +2596,12 @@ export type SelectQueryBuilderWithLeftJoin<
? LeftJoinedBuilder
: never
: TE extends keyof DB
- ? LeftJoinedBuilder
- : TE extends AliasedExpression
- ? LeftJoinedBuilder
- : TE extends (qb: any) => AliasedExpression
- ? LeftJoinedBuilder
- : never
+ ? LeftJoinedBuilder
+ : TE extends AliasedExpression
+ ? LeftJoinedBuilder
+ : TE extends (qb: any) => AliasedExpression
+ ? LeftJoinedBuilder
+ : never
type LeftJoinedBuilder<
DB,
@@ -2558,8 +2618,8 @@ type LeftJoinedDB = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? Nullable
: C extends keyof DB
- ? DB[C]
- : never
+ ? DB[C]
+ : never
}>
export type SelectQueryBuilderWithRightJoin<
@@ -2572,12 +2632,12 @@ export type SelectQueryBuilderWithRightJoin<
? RightJoinedBuilder
: never
: TE extends keyof DB
- ? RightJoinedBuilder
- : TE extends AliasedExpression
- ? RightJoinedBuilder
- : TE extends (qb: any) => AliasedExpression
- ? RightJoinedBuilder
- : never
+ ? RightJoinedBuilder
+ : TE extends AliasedExpression
+ ? RightJoinedBuilder
+ : TE extends (qb: any) => AliasedExpression
+ ? RightJoinedBuilder
+ : never
type RightJoinedBuilder<
DB,
@@ -2596,10 +2656,10 @@ type RightJoinedDB<
[C in keyof DB | A]: C extends A
? R
: C extends TB
- ? Nullable
- : C extends keyof DB
- ? DB[C]
- : never
+ ? Nullable