From b294eace397c5fbe0b997d23f6e368bae4ccefb0 Mon Sep 17 00:00:00 2001 From: Divyendu Singh Date: Sun, 7 May 2023 14:43:31 +0200 Subject: [PATCH 1/2] feat(pg-tle-mode): deploy the functions from a provided output folder as a pg_tle extension --- README.md | 12 ++-- src/commands/deploy.ts | 119 +++++++++++++++++++++++++++++----------- src/helpers/ParseCLI.ts | 17 ++++++ 3 files changed, 111 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 6248e5f..ab27c25 100644 --- a/README.md +++ b/README.md @@ -52,16 +52,20 @@ Generate PLV8 functions for an input typescript file | --scope-prefix | String | Specify a scope prefix, by default `plv8ify`, adds `plv8ify_` as prefix for exported typescript functions | `plv8ify` | | --pg-function-delimiter | String | Specify a delimiter for the generated Postgres function | `$plv8ify$` | | --fallback-type | String | Specify a fallback type when `plv8ify` fails to map a detected Typescript type to a Postges type | `JSONB` | -| --mode | 'inline', 'bundle' or 'start_proc' | 'inline' will bundle the library in each function, both 'bundle' and 'start_proc' creates a `{prefix}_init` function that loads the library. 'bundle' adds a check to each function to call 'init' if required, whereas 'start_proc' is designed to be used with plv8.start_proc | `inline` | +| --mode | 'inline' or 'bundle' or 'start_proc' | 'inline' will bundle the library in each function, both 'bundle' and 'start_proc' creates a `{prefix}_init` function that loads the library. 'bundle' adds a check to each function to call 'init' if required, whereas 'start_proc' is designed to be used with plv8.start_proc | `inline` | | --volatility | 'IMMUTABLE' or 'STABLE' or 'VOLATILE' | Change the volatility of all the generated functions. To change volatility of a specific function use the comment format `//@plv8ify-volatility-STABLE` in the input typescript file (see `examples/turf-js/input.ts`). Note that for now only single-line comment syntax is supported. | `IMMUTABLE` | ### Deploy Deploy an output folder to a Postgres database (defined by env var `DATABASE_URL`) -| Generate Command Flags | Type | Description | Default | -| ---------------------- | ------ | ------------------------ | -------------- | -| --output-folder | String | Specify an output folder | `plv8ify-dist` | +| Generate Command Flags | Type | Description | Default | +| ------------------------------ | ----------------------- | ------------------------------------------------------ | ---------------------------------------------- | +| --output-folder | String | Specify an output folder | `plv8ify-dist` | +| --deploy-mode | 'functions' or 'pg_tle' | Choose to deploy as functions or as a pg_tle extension | `functions` | +| --pg-tle-extension-name | String | pg_tle extension name | `plv8ify_pg_tle` | +| --pg-tle-extension-version | String | pg_tle extension version | `0.1` | +| --pg-tle-extension-description | String | pg_tle extension description | `plv8ify_pg_tle <...some default description>` | ## Caveats diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index b7ef3e5..84cffa9 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -2,6 +2,7 @@ import dotenv from 'dotenv' import fs from 'fs' import path from 'path' import task from 'tasuku' +import { match } from 'ts-pattern' import { Database } from '../helpers/Database' import { ParseCLI } from '../helpers/ParseCLI' @@ -64,43 +65,95 @@ export async function deployCommand( } const db = database.getConnection() - let deployCommands = fs - .readdirSync(outputFolderPath) - // Only extract .plv8.sql files, this will need to change if we ever make the extension configurable - .filter((file) => file.endsWith('.plv8.sql')) - .map((file) => { - const filePath = path.join(outputFolderPath, file) - return { - filePath, - sqlQueryPromise: db.file(filePath), - } - }) - await task( - `Deploying files from ${outputFolderPath} to the provided PostgreSQL database 🚧`.trim(), - async ({ setWarning }) => { - const taskGroup = await task.group((task) => - deployCommands.map((deployCommand) => { - const name = getFunctionNameFromFilePath(deployCommand.filePath) - return task( - `Deploying ${name}`, - async ({ setTitle: _setTitle, setError: _setError }) => { - try { - await deployCommand.sqlQueryPromise - _setTitle(`Deployed ${name}`) - } catch (e) { - _setError(`Failed to deploy ${name} (because of ${e.message})`) - setWarning(`Failed to some functions (see below))`) - } - } + await match(CLI.config.deployMode) + .with('functions', async () => { + const deployCommands = fs + .readdirSync(outputFolderPath) + // Only extract .plv8.sql files, this will need to change if we ever make the extension configurable + .filter((file) => file.endsWith('.plv8.sql')) + .map((file) => { + const filePath = path.join(outputFolderPath, file) + return { + filePath, + sqlQueryPromise: db.file(filePath), + } + }) + + await task( + `Deploying files from ${outputFolderPath} to the provided PostgreSQL database 🚧`.trim(), + async ({ setWarning }) => { + const taskGroup = await task.group((task) => + deployCommands.map((deployCommand) => { + const name = getFunctionNameFromFilePath(deployCommand.filePath) + return task( + `Deploying ${name}`, + async ({ setTitle: _setTitle, setError: _setError }) => { + try { + await deployCommand.sqlQueryPromise + _setTitle(`Deployed ${name}`) + } catch (e) { + _setError( + `Failed to deploy ${name} (because of ${e.message})` + ) + setWarning(`Failed to some functions (see below))`) + } + } + ) + }) ) + + // TODO: add some batching here + await Promise.allSettled(taskGroup) + } + ) + }) + .with('pg_tle', async () => { + const deployableFunctionsSQL = fs + .readdirSync(outputFolderPath) + // Only extract .plv8.sql files, this will need to change if we ever make the extension configurable + .filter((file) => file.endsWith('.plv8.sql')) + .map((file) => { + const filePath = path.join(outputFolderPath, file) + const fileContents = fs.readFileSync(filePath, 'utf-8') + return fileContents }) + .join('\n') + await task( + `Deploying files from ${outputFolderPath} to the provided PostgreSQL database as a pg_tle extension 🚧`.trim(), + async ({ setTitle, setError }) => { + try { + const extensionName = CLI.config.pgTLEExtensionName + const extensionVersion = CLI.config.pgTLEExtensionVersion + const extensionDescription = CLI.config.pgTLEExtensionDescrption + await db.begin(async (db) => { + await db`CREATE EXTENSION IF NOT EXISTS pg_tle` + await db`DROP EXTENSION IF EXISTS ${db(extensionName)}` + await db`SELECT pgtle.uninstall_extension_if_exists(${extensionName})` + await db`SELECT pgtle.install_extension( + ${extensionName}, + ${extensionVersion}, + ${extensionDescription}, + ${deployableFunctionsSQL} + )` + await db`CREATE EXTENSION IF NOT EXISTS ${db(extensionName)}` + }) + await db.listen('notices', (payload) => { + console.log({ payload }) + }) + setTitle( + `Deployed files from ${outputFolderPath} to the provided PostgreSQL database as a pg_tle extension ✅` + ) + } catch (e) { + setError( + 'Failed to deploy the functions in the provided output folder as a pg_tle extension because: ' + + e + ) + } + } ) - - // TODO: add some batching here - await Promise.allSettled(taskGroup) - } - ) + }) + .exhaustive() database.endConnection() } diff --git a/src/helpers/ParseCLI.ts b/src/helpers/ParseCLI.ts index 47a14e8..2af8397 100644 --- a/src/helpers/ParseCLI.ts +++ b/src/helpers/ParseCLI.ts @@ -1,6 +1,7 @@ import arg from 'arg' type Command = 'version' | 'generate' | 'deploy' +type DeployMode = 'functions' | 'pg_tle' export class ParseCLI { static getCommand() { @@ -16,6 +17,11 @@ export class ParseCLI { '--mode': String, '--volatility': String, '--debug': Boolean, + + '--deploy-mode': String, + '--pg-tle-extension-name': String, + '--pg-tle-extension-version': String, + '--pg-tle-extension-description': String, }) if (args._.length === 0) { @@ -35,6 +41,13 @@ Please specify a command. Available commands: generate, version, deploy const mode = (args['--mode'] || 'inline') as Mode const defaultVolatility = (args['--volatility'] || 'IMMUTABLE') as Volatility + const deployMode = (args['--deploy-mode'] || 'functions') as DeployMode + const pgTLEExtensionName = (args['--pg-tle-extension-name'] || + 'plv8ify_pg_tle') as string + const pgTLEExtensionVersion = (args['--pg-tle-extension-version'] || + '0.1') as string + const pgTLEExtensionDescription = (args['--pg-tle-extension-description'] || + 'plv8ify_pg_tle extension is the default name of extensions deployed with plv8ify, please pass --pg-tle-extension-name as a CLI argument to override this') as string return { command: args._[0] as Command, @@ -47,6 +60,10 @@ Please specify a command. Available commands: generate, version, deploy fallbackReturnType, mode, defaultVolatility, + deployMode, + pgTLEExtensionName, + pgTLEExtensionVersion, + pgTLEExtensionDescrption: pgTLEExtensionDescription, }, } } From 8c91d1da814150f6d0e26b615623a615af61b7a0 Mon Sep 17 00:00:00 2001 From: Divyendu Singh Date: Sun, 7 May 2023 14:54:18 +0200 Subject: [PATCH 2/2] feat(pg-tle-mode): fix typo --- src/commands/deploy.ts | 2 +- src/helpers/ParseCLI.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index 84cffa9..013c803 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -125,7 +125,7 @@ export async function deployCommand( try { const extensionName = CLI.config.pgTLEExtensionName const extensionVersion = CLI.config.pgTLEExtensionVersion - const extensionDescription = CLI.config.pgTLEExtensionDescrption + const extensionDescription = CLI.config.pgTLEExtensionDescription await db.begin(async (db) => { await db`CREATE EXTENSION IF NOT EXISTS pg_tle` await db`DROP EXTENSION IF EXISTS ${db(extensionName)}` diff --git a/src/helpers/ParseCLI.ts b/src/helpers/ParseCLI.ts index 2af8397..3291282 100644 --- a/src/helpers/ParseCLI.ts +++ b/src/helpers/ParseCLI.ts @@ -63,7 +63,7 @@ Please specify a command. Available commands: generate, version, deploy deployMode, pgTLEExtensionName, pgTLEExtensionVersion, - pgTLEExtensionDescrption: pgTLEExtensionDescription, + pgTLEExtensionDescription, }, } }