Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'pgIdentifiers' setting to allow _not_ fully qualifying table/function names #1774

Merged
merged 6 commits into from
Sep 29, 2023
Merged
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
9 changes: 9 additions & 0 deletions .changeset/violet-adults-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"graphile-build-pg": patch
"postgraphile": patch
---

Add `preset.gather.pgIdentifiers` setting (values: 'qualified' or
'unqualified'); if set to 'unqualified' then we will not add the schema name to
table or function identifiers - it's up to you to ensure they're present in the
`search_path` (which you can set via `pgSettings` on a per-request basis).
50 changes: 49 additions & 1 deletion graphile-build/graphile-build-pg/src/plugins/PgBasicsPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import "graphile-build";
import "./PgTablesPlugin.js";
import "../interfaces.js";
import "graphile-config";
Expand All @@ -14,6 +13,8 @@ import type {
} from "@dataplan/pg";
import * as dataplanPg from "@dataplan/pg";
import type { GraphQLType } from "grafast/graphql";
import { EXPORTABLE, gatherConfig } from "graphile-build";
import type { SQL } from "pg-sql2";
import sql from "pg-sql2";

import { getBehavior } from "../behavior.js";
Expand Down Expand Up @@ -114,6 +115,22 @@ declare global {
pgCodecRef: PgCodecRef;
pgRefDefinition: PgRefDefinition;
}
interface GatherOptions {
/** Set to 'unqualified' to omit the schema name from table, function, and type identifiers */
pgIdentifiers?: "qualified" | "unqualified";
}
}

namespace GraphileConfig {
interface GatherHelpers {
pgBasics: {
/**
* Create an SQL identifier from the given parts; skipping the very
* first part (schema) if pgIdentifiers is set to 'unqualified'
*/
identifier(...parts: string[]): SQL;
};
}
}
}

Expand All @@ -123,6 +140,37 @@ export const PgBasicsPlugin: GraphileConfig.Plugin = {
"Basic utilities required by many other graphile-build-pg plugins.",
version: version,

gather: gatherConfig({
namespace: "pgBasics",
helpers: {
identifier(info, ...parts) {
switch (info.options.pgIdentifiers) {
case "unqualified": {
// strip the schema
const [, ...partsWithoutSchema] = parts;
return EXPORTABLE(
(partsWithoutSchema, sql) =>
sql.identifier(...partsWithoutSchema),
[partsWithoutSchema, sql],
);
}
case "qualified":
case undefined: {
return EXPORTABLE(
(parts, sql) => sql.identifier(...parts),
[parts, sql],
);
}
default: {
throw new Error(
`Setting preset.gather.pgIdentifiers had unsupported value '${info.options.pgIdentifiers}'; please use a supported value: 'qualified' or 'unqualified'.`,
);
}
}
},
},
}),

schema: {
globalBehavior: "connection -list",
entityBehavior: {
Expand Down
90 changes: 32 additions & 58 deletions graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
} from "@dataplan/pg";
import { EXPORTABLE, gatherConfig } from "graphile-build";
import type { PgAttribute, PgClass, PgType } from "pg-introspection";
import sql from "pg-sql2";

import { addBehaviorToTags } from "../utils.js";
import { version } from "../version.js";
Expand Down Expand Up @@ -406,33 +405,30 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = {
};
const executor =
info.helpers.pgIntrospection.getExecutorForService(serviceName);
const sqlIdent = info.helpers.pgBasics.identifier(nspName, className);
const spec: PgRecordTypeCodecSpec<any, any> = EXPORTABLE(
(
attributes,
className,
codecName,
description,
executor,
extensions,
nspName,
sql,
sqlIdent,
) => ({
name: codecName,
identifier: sql.identifier(nspName, className),
identifier: sqlIdent,
attributes,
description,
extensions,
executor,
}),
[
attributes,
className,
codecName,
description,
executor,
extensions,
nspName,
sql,
sqlIdent,
],
);
await info.process("pgCodecs_recordType_spec", {
Expand Down Expand Up @@ -564,31 +560,19 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = {
pgType: type,
extensions,
});
const sqlIdent = info.helpers.pgBasics.identifier(
namespaceName,
typeName,
);
return EXPORTABLE(
(
codecName,
enumCodec,
enumLabels,
extensions,
namespaceName,
sql,
typeName,
) =>
(codecName, enumCodec, enumLabels, extensions, sqlIdent) =>
enumCodec({
name: codecName,
identifier: sql.identifier(namespaceName, typeName),
identifier: sqlIdent,
values: enumLabels,
extensions,
}),
[
codecName,
enumCodec,
enumLabels,
extensions,
namespaceName,
sql,
typeName,
],
[codecName, enumCodec, enumLabels, extensions, sqlIdent],
);
}

Expand Down Expand Up @@ -640,35 +624,30 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = {
extensions,
});

const sqlIdent = info.helpers.pgBasics.identifier(
namespaceName,
typeName,
);
return EXPORTABLE(
(
codecName,
description,
extensions,
innerCodec,
namespaceName,
rangeOfCodec,
sql,
typeName,
sqlIdent,
) =>
rangeOfCodec(
innerCodec,
codecName,
sql.identifier(namespaceName, typeName),
{
description,
extensions,
},
),
rangeOfCodec(innerCodec, codecName, sqlIdent, {
description,
extensions,
}),
[
codecName,
description,
extensions,
innerCodec,
namespaceName,
rangeOfCodec,
sql,
typeName,
sqlIdent,
],
);
}
Expand Down Expand Up @@ -713,38 +692,33 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = {
pgType: type,
serviceName,
});
const sqlIdent = info.helpers.pgBasics.identifier(
namespaceName,
typeName,
);
return EXPORTABLE(
(
codecName,
description,
domainOfCodec,
extensions,
innerCodec,
namespaceName,
notNull,
sql,
typeName,
sqlIdent,
) =>
domainOfCodec(
innerCodec,
codecName,
sql.identifier(namespaceName, typeName),
{
description,
extensions,
notNull,
},
) as PgCodec,
domainOfCodec(innerCodec, codecName, sqlIdent, {
description,
extensions,
notNull,
}) as PgCodec,
[
codecName,
description,
domainOfCodec,
extensions,
innerCodec,
namespaceName,
notNull,
sql,
typeName,
sqlIdent,
],
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,11 @@ export const PgEnumTablesPlugin: GraphileConfig.Plugin = {
sql.fragment`select ${sql.join(
attributes.map((col) => sql.identifier(col.attname)),
", ",
)} from ${sql.identifier(
pgClass.getNamespace()!.nspname,
pgClass.relname,
)};`,
)} from ${
// NOTE: Even in the case of unqualified pgIdentifiers, we still want
// to read _this_ enums values from _this_ schema.
sql.identifier(pgClass.getNamespace()!.nspname, pgClass.relname)
};`,
);

const pgService = info.resolvedPreset.pgServices!.find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,15 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = {
const namespaceName = namespace.nspname;
const procName = pgProc.proname;

const sqlIdent = info.helpers.pgBasics.identifier(
namespaceName,
procName,
);
const fromCallback = EXPORTABLE(
(namespaceName, procName, sql, sqlFromArgDigests) =>
(sql, sqlFromArgDigests, sqlIdent) =>
(...args: PgSelectArgumentDigest[]) =>
sql`${sql.identifier(
namespaceName,
procName,
)}(${sqlFromArgDigests(args)})`,
[namespaceName, procName, sql, sqlFromArgDigests],
sql`${sqlIdent}(${sqlFromArgDigests(args)})`,
[sql, sqlFromArgDigests, sqlIdent],
);

addBehaviorToTags(tags, "-filter -order", true);
Expand Down
19 changes: 18 additions & 1 deletion postgraphile/postgraphile/__tests__/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ export async function runTestQuery(
setupSql?: string;
cleanupSql?: string;
extends?: string | string[];
pgIdentifiers?: "qualified" | "unqualified";
search_path?: string;
},
options: {
callback?: (
Expand All @@ -194,7 +196,14 @@ export async function runTestQuery(
queries: PgClientQuery[];
extensions?: any;
}> {
const { variableValues, graphileBuildOptions, setupSql, cleanupSql } = config;
const {
variableValues,
graphileBuildOptions,
setupSql,
cleanupSql,
pgIdentifiers,
search_path,
} = config;
const { path } = options;

const queries: PgClientQuery[] = [];
Expand Down Expand Up @@ -243,7 +252,12 @@ export async function runTestQuery(
? () => ({
role: "postgraphile_test_visitor",
"jwt.claims.user_id": "3",
search_path,
})
: search_path
? {
search_path,
}
: undefined,
schemas: schemas,
adaptorSettings: {
Expand All @@ -256,6 +270,9 @@ export async function runTestQuery(
config.setofFunctionsContainNulls === false,
...graphileBuildOptions,
},
gather: {
pgIdentifiers,
},
grafast: {
explain: ["plan"],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
createMeasurement: {
measurement: {
timestamp: "2023-05-24T03:43:00.000000-04:00",
key: "temp",
value: 12.7,
userByUserId: {
id: 4,
name: "Dave",
},
},
},
updateMeasurementByTimestampAndKey: {
measurement: {
timestamp: "2023-05-24T03:43:00.000000-04:00",
key: "temp",
value: 13,
userByUserId: {
id: 4,
name: "Dave",
},
},
},
}
Loading