Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -768,8 +768,9 @@ drop policy if exists access_by_numbers on mytable;
create policy access_by_numbers on mytable for update using (myfunction(4, 2) < 42);
```
and when the migration is committed or watched, the contents of `myfunction.sql`
will be included in the result, such that the following SQL is executed:
and when the migration is committed, watched, run, or compiled, the contents of
`myfunction.sql` will be included in the result, such that the following SQL is
executed:
```sql
create or replace function myfunction(a int, b int)
Expand Down
38 changes: 29 additions & 9 deletions __tests__/compile.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import "./helpers";

import * as mockFs from "mock-fs";
import mockFs from "mock-fs";

import { compile } from "../src";
import { compile, Settings } from "../src";
let old: string | undefined;
beforeAll(() => {
let settings: Settings;
beforeAll(async () => {
old = process.env.DATABASE_AUTHENTICATOR;
process.env.DATABASE_AUTHENTICATOR = "dbauth";
settings = {
connectionString: "postgres://dbowner:dbpassword@dbhost:1221/dbname",
placeholders: {
":DATABASE_AUTHENTICATOR": "!ENV",
},
};
});
afterAll(() => {
process.env.DATABASE_AUTHENTICATOR = old;
Expand All @@ -19,12 +26,7 @@ afterEach(() => {
it("compiles SQL with settings", async () => {
expect(
await compile(
{
connectionString: "postgres://dbowner:dbpassword@dbhost:1221/dbname",
placeholders: {
":DATABASE_AUTHENTICATOR": "!ENV",
},
},
settings,
`\
BEGIN;
GRANT CONNECT ON DATABASE :DATABASE_NAME TO :DATABASE_OWNER;
Expand All @@ -38,6 +40,7 @@ CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public;
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
COMMIT;
`,
"stdin",
),
).toEqual(`\
BEGIN;
Expand All @@ -53,3 +56,20 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
COMMIT;
`);
});

it("will compile included files", async () => {
mockFs({
"migrations/fixtures/foo.sql": "select * from foo;",
});
expect(
await compile(
settings,
`\
--!include foo.sql
`,
`${process.cwd()}/migrations/current.sql`,
),
).toEqual(`\
select * from foo;
`);
});
24 changes: 17 additions & 7 deletions src/commands/compile.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as fsp from "fs/promises";
import { resolve } from "path";
import { CommandModule } from "yargs";

import { compilePlaceholders } from "../migration";
import { compileIncludes, compilePlaceholders } from "../migration";
import { parseSettings, Settings } from "../settings";
import { CommonArgv, getSettings, readStdin } from "./_common";

Expand All @@ -12,10 +13,16 @@ interface CompileArgv extends CommonArgv {
export async function compile(
settings: Settings,
content: string,
filename: string,
shadow = false,
): Promise<string> {
const parsedSettings = await parseSettings(settings, shadow);
return compilePlaceholders(parsedSettings, content, shadow);
const parsedContent = await compileIncludes(
parsedSettings,
content,
new Set([filename]),
);
return compilePlaceholders(parsedSettings, parsedContent, shadow);
}

export const compileCommand: CommandModule<
Expand All @@ -25,7 +32,7 @@ export const compileCommand: CommandModule<
command: "compile [file]",
aliases: [],
describe: `\
Compiles a SQL file, inserting all the placeholders and returning the result to STDOUT`,
Compiles a SQL file, resolving includes, inserting all the placeholders and returning the result to STDOUT`,
builder: {
shadow: {
type: "boolean",
Expand All @@ -35,12 +42,15 @@ Compiles a SQL file, inserting all the placeholders and returning the result to
},
handler: async (argv) => {
const settings = await getSettings({ configFile: argv.config });
const content =
const { content, filename } =
typeof argv.file === "string"
? await fsp.readFile(argv.file, "utf8")
: await readStdin();
? {
filename: resolve(argv.file),
content: await fsp.readFile(argv.file, "utf8"),
}
: { filename: "stdin", content: await readStdin() };

const compiled = await compile(settings, content, argv.shadow);
const compiled = await compile(settings, content, filename, argv.shadow);

// eslint-disable-next-line no-console
console.log(compiled);
Expand Down
14 changes: 10 additions & 4 deletions src/commands/run.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as fsp from "fs/promises";
import { resolve } from "path";
import { QueryResultRow } from "pg";
import { CommandModule } from "yargs";

import { DO_NOT_USE_DATABASE_URL } from "../actions";
import { runQueryWithErrorInstrumentation } from "../instrumentation";
import { compilePlaceholders } from "../migration";
import { compileIncludes, compilePlaceholders } from "../migration";
import { withClient } from "../pgReal";
import {
makeRootDatabaseConnectionString,
Expand Down Expand Up @@ -35,7 +36,12 @@ export async function run<T extends QueryResultRow = QueryResultRow>(
} = {},
): Promise<T[] | undefined> {
const parsedSettings = await parseSettings(settings, shadow);
const sql = compilePlaceholders(parsedSettings, content, shadow);
const parsedContent = await compileIncludes(
parsedSettings,
content,
new Set([filename]),
);
const sql = compilePlaceholders(parsedSettings, parsedContent, shadow);
const baseConnectionString = rootDatabase
? parsedSettings.rootConnectionString
: shadow
Expand All @@ -62,7 +68,7 @@ export const runCommand: CommandModule<Record<string, never>, RunArgv> = {
command: "run [file]",
aliases: [],
describe: `\
Compiles a SQL file, inserting all the placeholders, and then runs it against the database. Useful for seeding. If called from an action will automatically run against the same database (via GM_DBURL envvar) unless --shadow or --rootDatabase are supplied.`,
Compiles a SQL file, resolving includes, inserting all the placeholders, and then runs it against the database. Useful for seeding. If called from an action will automatically run against the same database (via GM_DBURL envvar) unless --shadow or --rootDatabase are supplied.`,
builder: {
shadow: {
type: "boolean",
Expand Down Expand Up @@ -105,7 +111,7 @@ Compiles a SQL file, inserting all the placeholders, and then runs it against th
const { content, filename } =
typeof argv.file === "string"
? {
filename: argv.file,
filename: resolve(argv.file),
content: await fsp.readFile(argv.file, "utf8"),
}
: { filename: "stdin", content: await readStdin() };
Expand Down