diff --git a/package.json b/package.json index da67bb071..cbd571092 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "script:copy-interface-doc": "node scripts/copy-interface-documentation.js", "script:add-deno-type-references": "node scripts/add-deno-type-references.js", "script:align-site-version": "node scripts/align-site-version.js", + "script:generate-site-examples": "node scripts/generate-site-examples.js", "prepublishOnly": "npm run build" }, "author": "Sami Koskimäki ", diff --git a/scripts/generate-site-examples.js b/scripts/generate-site-examples.js index 4e58e42fd..a667470ab 100644 --- a/scripts/generate-site-examples.js +++ b/scripts/generate-site-examples.js @@ -71,6 +71,24 @@ const moreExamplesByCategory = { 'returning method': 'https://kysely-org.github.io/kysely-apidoc/classes/DeleteQueryBuilder.html#returning', }, + merge: { + 'mergeInto method': + 'https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#mergeInto', + 'using method': + 'https://kysely-org.github.io/kysely-apidoc/classes/MergeQueryBuilder.html#using', + 'whenMatched method': + 'https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenMatched', + 'thenUpdateSet method': + 'https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenUpdateSet', + 'thenDelete method': + 'https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDelete', + 'thenDoNothing method': + 'https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDoNothing', + 'whenNotMatched method': + 'https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenNotMatched', + 'thenInsertValues method': + 'https://kysely-org.github.io/kysely-apidoc/classes/NotMatchedThenableMergeQueryBuilder.html#thenInsertValues', + }, transactions: { 'transaction method': 'https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#transaction', @@ -88,6 +106,7 @@ function main() { const lines = readLines(filePath) const state = { filePath, + /** @type {string | null} */ line: null, lineIndex: 0, annotation: null, @@ -153,7 +172,8 @@ function writeSiteExample(state) { const codeVariable = _.camelCase(name) const fileName = `${priority.padStart(4, '0')}-${_.kebabCase(name)}` - const filePath = path.join(SITE_EXAMPLE_PATH, category, fileName) + const folderPath = path.join(SITE_EXAMPLE_PATH, category) + const filePath = path.join(folderPath, fileName) const codeFile = `export const ${codeVariable} = \`${deindent(code) .replaceAll('`', '\\`') @@ -197,7 +217,22 @@ function writeSiteExample(state) { const exampleFile = parts.join('\n') - fs.writeFileSync(filePath + '.js', codeFile) + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath, { recursive: true }) + fs.writeFileSync( + path.join(folderPath, '_category_.json'), + `{ + "label": "${_.startCase(category)}", + "position": 0, // TODO: set the position of the category. + "link": { + "type": "generated-index", + "description": "Short and simple examples of using the ${category} functionality." // TODO: review this. + } +}`, + ) + } + + fs.writeFileSync(filePath + '.js', codeFile, {}) fs.writeFileSync(filePath + '.mdx', exampleFile) } @@ -298,10 +333,10 @@ function deindent(str) { let ws = Number.MAX_SAFE_INTEGER for (const line of lines) { if (line.trim().length > 0) { - const wsExec = /^(\s*)/.exec(line) + const [, wsExec] = /^(\s*)/.exec(line) || [] - if (wsExec[1].length < ws) { - ws = wsExec[1].length + if (wsExec != null && wsExec.length < ws) { + ws = wsExec.length } } } diff --git a/site/docs/examples/cte/_category_.json b/site/docs/examples/cte/_category_.json index da9e25b76..a4fc30836 100644 --- a/site/docs/examples/cte/_category_.json +++ b/site/docs/examples/cte/_category_.json @@ -1,6 +1,6 @@ { "label": "CTE", - "position": 7, + "position": 9, "link": { "type": "generated-index", "description": "Short and simple examples of how to use Common Table Expressions (CTE) in queries." diff --git a/site/docs/examples/merge/0010-source-row-existence.js b/site/docs/examples/merge/0010-source-row-existence.js new file mode 100644 index 000000000..b6c17278f --- /dev/null +++ b/site/docs/examples/merge/0010-source-row-existence.js @@ -0,0 +1,10 @@ +export const sourceRowExistence = `const result = await db + .mergeInto('person as target') + .using('pet as source', 'source.owner_id', 'target.id') + .whenMatchedAnd('target.has_pets', '!=', 'Y') + .thenUpdateSet({ has_pets: 'Y' }) + .whenNotMatchedBySourceAnd('target.has_pets', '=', 'Y') + .thenUpdateSet({ has_pets: 'N' }) + .executeTakeFirstOrThrow() + +console.log(result.numChangedRows)` \ No newline at end of file diff --git a/site/docs/examples/merge/0010-source-row-existence.mdx b/site/docs/examples/merge/0010-source-row-existence.mdx new file mode 100644 index 000000000..7ddfb64b2 --- /dev/null +++ b/site/docs/examples/merge/0010-source-row-existence.mdx @@ -0,0 +1,36 @@ +--- +title: 'Source row existence' +--- + +# Source row existence + +Update a target column based on the existence of a source row: + +import { + Playground, + exampleSetup, +} from '../../../src/components/Playground' + +import { + sourceRowExistence +} from './0010-source-row-existence' + +
+ +
+ +:::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: + - [mergeInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#mergeInto) + - [using method](https://kysely-org.github.io/kysely-apidoc/classes/MergeQueryBuilder.html#using) + - [whenMatched method](https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenMatched) + - [thenUpdateSet method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenUpdateSet) + - [thenDelete method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDelete) + - [thenDoNothing method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDoNothing) + - [whenNotMatched method](https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenNotMatched) + - [thenInsertValues method](https://kysely-org.github.io/kysely-apidoc/classes/NotMatchedThenableMergeQueryBuilder.html#thenInsertValues) +::: diff --git a/site/docs/examples/merge/0020-temporary-changes-table.js b/site/docs/examples/merge/0020-temporary-changes-table.js new file mode 100644 index 000000000..693843160 --- /dev/null +++ b/site/docs/examples/merge/0020-temporary-changes-table.js @@ -0,0 +1,23 @@ +export const temporaryChangesTable = `const result = await db + .mergeInto("wine as target") + .using( + "wine_stock_change as source", + "source.wine_name", + "target.name", + ) + .whenNotMatchedAnd("source.stock_delta", ">", 0) + .thenInsertValues(({ ref }) => ({ + name: ref("source.wine_name"), + stock: ref("source.stock_delta"), + })) + .whenMatchedAnd( + (eb) => eb("target.stock", "+", eb.ref("source.stock_delta")), + ">", + 0, + ) + .thenUpdateSet("stock", (eb) => + eb("target.stock", "+", eb.ref("source.stock_delta")), + ) + .whenMatched() + .thenDelete() + .executeTakeFirstOrThrow()` \ No newline at end of file diff --git a/site/docs/examples/merge/0020-temporary-changes-table.mdx b/site/docs/examples/merge/0020-temporary-changes-table.mdx new file mode 100644 index 000000000..1339f0a56 --- /dev/null +++ b/site/docs/examples/merge/0020-temporary-changes-table.mdx @@ -0,0 +1,36 @@ +--- +title: 'Temporary changes table' +--- + +# Temporary changes table + +Merge new entries from a temporary changes table: + +import { + Playground, + exampleSetup, +} from '../../../src/components/Playground' + +import { + temporaryChangesTable +} from './0020-temporary-changes-table' + +
+ +
+ +:::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: + - [mergeInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#mergeInto) + - [using method](https://kysely-org.github.io/kysely-apidoc/classes/MergeQueryBuilder.html#using) + - [whenMatched method](https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenMatched) + - [thenUpdateSet method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenUpdateSet) + - [thenDelete method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDelete) + - [thenDoNothing method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDoNothing) + - [whenNotMatched method](https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenNotMatched) + - [thenInsertValues method](https://kysely-org.github.io/kysely-apidoc/classes/NotMatchedThenableMergeQueryBuilder.html#thenInsertValues) +::: diff --git a/site/docs/examples/merge/_category_.json b/site/docs/examples/merge/_category_.json new file mode 100644 index 000000000..add6bbea8 --- /dev/null +++ b/site/docs/examples/merge/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "MERGE", + "position": 7, + "link": { + "type": "generated-index", + "description": "Short and simple examples of how to write MERGE queries." + } +} diff --git a/site/docs/examples/select/0070-distinct.mdx b/site/docs/examples/select/0070-distinct.mdx index 21ee14276..8efea1b06 100644 --- a/site/docs/examples/select/0070-distinct.mdx +++ b/site/docs/examples/select/0070-distinct.mdx @@ -4,8 +4,6 @@ title: 'Distinct' # Distinct -### Examples - import { Playground, exampleSetup, diff --git a/site/docs/examples/transactions/_category_.json b/site/docs/examples/transactions/_category_.json index c304bc27f..8fa52772e 100644 --- a/site/docs/examples/transactions/_category_.json +++ b/site/docs/examples/transactions/_category_.json @@ -1,6 +1,6 @@ { "label": "Transactions", - "position": 6, + "position": 8, "link": { "type": "generated-index", "description": "Short and simple examples of how to use transactions." diff --git a/site/package-lock.json b/site/package-lock.json index b914cdab3..3e3f5dcbc 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -1,6 +1,6 @@ { "name": "kysely", - "version": "0.27.3", + "version": "0.27.4", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/site/package.json b/site/package.json index 4df8b8e1a..953a1ad11 100644 --- a/site/package.json +++ b/site/package.json @@ -1,6 +1,6 @@ { "name": "kysely", - "version": "0.27.3", + "version": "0.27.4", "private": true, "scripts": { "docusaurus": "docusaurus", diff --git a/site/src/components/Playground.tsx b/site/src/components/Playground.tsx index 17c664000..b32443a81 100644 --- a/site/src/components/Playground.tsx +++ b/site/src/components/Playground.tsx @@ -77,8 +77,10 @@ interface PlaygroundState { export const exampleSetup = `import { Generated } from 'kysely' export interface Database { - person: PersonTable - pet: PetTable + person: PersonTable + pet: PetTable + wine: WineTable + wine_stock_change: WineStockChangeTable } interface PersonTable { @@ -87,6 +89,7 @@ interface PersonTable { last_name: string | null created_at: Generated age: number + has_pets: Generated<'Y' | 'N'> } interface PetTable { @@ -96,4 +99,16 @@ interface PetTable { species: 'cat' | 'dog' is_favorite: boolean } + +interface WineTable { + id: Generated + name: string + stock: number +} + +interface WineStockChangeTable { + id: Generated + wine_name: string + stock_delta: number +} ` diff --git a/site/src/css/custom.css b/site/src/css/custom.css index 45b341e82..2b9ea25d0 100644 --- a/site/src/css/custom.css +++ b/site/src/css/custom.css @@ -37,6 +37,11 @@ -14.8px 50.6px 59.3px -2.5px hsl(var(--shadow-color) / 0.1); } +/* remove default theme max-width in examples content */ +:root [class^='col docItemCol_'] { + max-width: unset !important; +} + [data-theme='dark'] { --ifm-color-primary: var(--sky9); --ifm-color-primary-dark: var(--sky10); diff --git a/src/query-builder/select-query-builder.ts b/src/query-builder/select-query-builder.ts index 29dd89be7..80de9abac 100644 --- a/src/query-builder/select-query-builder.ts +++ b/src/query-builder/select-query-builder.ts @@ -454,10 +454,10 @@ export interface SelectQueryBuilder /** * Makes the selection distinct. * - * - * * ### Examples * + * + * * ```ts * const persons = await db.selectFrom('person') * .select('first_name') diff --git a/src/query-creator.ts b/src/query-creator.ts index 5291fee28..6292263e2 100644 --- a/src/query-creator.ts +++ b/src/query-creator.ts @@ -512,14 +512,18 @@ export class QueryCreator { * * ### Examples * + * + * + * Update a target column based on the existence of a source row: + * * ```ts * const result = await db - * .mergeInto('person') - * .using('pet', 'pet.owner_id', 'person.id') - * .whenMatched((and) => and('has_pets', '!=', 'Y')) + * .mergeInto('person as target') + * .using('pet as source', 'source.owner_id', 'target.id') + * .whenMatchedAnd('target.has_pets', '!=', 'Y') * .thenUpdateSet({ has_pets: 'Y' }) - * .whenNotMatched() - * .thenDoNothing() + * .whenNotMatchedBySourceAnd('target.has_pets', '=', 'Y') + * .thenUpdateSet({ has_pets: 'N' }) * .executeTakeFirstOrThrow() * * console.log(result.numChangedRows) @@ -529,11 +533,56 @@ export class QueryCreator { * * ```sql * merge into "person" - * using "pet" on "pet"."owner_id" = "person"."id" - * when matched and "has_pets" != $1 then - * update set "has_pets" = $2 - * when not matched then - * do nothing + * using "pet" + * on "pet"."owner_id" = "person"."id" + * when matched and "has_pets" != $1 + * then update set "has_pets" = $2 + * when not matched by source and "has_pets" = $3 + * then update set "has_pets" = $4 + * ``` + * + * + * + * Merge new entries from a temporary changes table: + * + * ```ts + * const result = await db + * .mergeInto("wine as target") + * .using( + * "wine_stock_change as source", + * "source.wine_name", + * "target.name", + * ) + * .whenNotMatchedAnd("source.stock_delta", ">", 0) + * .thenInsertValues(({ ref }) => ({ + * name: ref("source.wine_name"), + * stock: ref("source.stock_delta"), + * })) + * .whenMatchedAnd( + * (eb) => eb("target.stock", "+", eb.ref("source.stock_delta")), + * ">", + * 0, + * ) + * .thenUpdateSet("stock", (eb) => + * eb("target.stock", "+", eb.ref("source.stock_delta")), + * ) + * .whenMatched() + * .thenDelete() + * .executeTakeFirstOrThrow() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * merge into "wine" as "target" + * using "wine_stock_change" as "source" + * on "source"."wine_name" = "target"."name" + * when not matched and "source"."stock_delta" > $1 + * then insert ("name", "stock") values ("source"."wine_name", "source"."stock_delta") + * when matched and "target"."stock" + "source"."stock_delta" > $2 + * then update set "stock" = "target"."stock" + "source"."stock_delta" + * when matched + * then delete * ``` */ mergeInto(