import type { AnyObject } from "prostgles-types"; import { getSerialisableError, isObject, omitKeys, pickKeys } from "prostgles-types"; import type { DB, Prostgles } from "../Prostgles"; import { asNameAlias } from "../utils/asNameAlias"; import type { LocalParams, SortItem } from "./DboBuilderTypes"; import { pgp } from "./DboBuilderTypes"; import { getSchemaFilter } from "./schema/getTablesForSchemaPostgresSQL"; import type { ViewHandler } from "./ViewHandler/ViewHandler"; import type { ProstglesInitOptions } from "../ProstglesTypes"; import { sqlErrCodeToMsg } from "./sqlErrCodeToMsg"; import type { TableHandler } from "./TableHandler/TableHandler"; export const getErrorAsObject = (rawError: any) => { const serializedError = getSerialisableError(rawError); if (isObject(serializedError) && !Array.isArray(serializedError)) { return omitKeys(serializedError, ["stack"]); } return { message: serializedError }; }; type GetSerializedClientErrorFromPGErrorArgs = | { type: "sql"; localParams: LocalParams | undefined; prostgles: Prostgles; } | { type: "tableMethod"; localParams: LocalParams | undefined; view: ViewHandler | Partial | undefined; prostgles: Prostgles; allowedKeys?: string[]; } | { type: "method"; localParams: LocalParams | undefined; allowedKeys?: string[]; view?: undefined; prostgles: Prostgles; }; const otherKeys = [ "column", "code", "code_info", "table", "constraint", "severity", "message", "name", ] as const; export function getSerializedClientErrorFromPGError( rawError: any, args: GetSerializedClientErrorFromPGErrorArgs, ): AnyObject { const err = getErrorAsObject(rawError); if (err.code) { err.code_info = sqlErrCodeToMsg(err.code); } if (process.env.PRGL_DEBUG) { console.trace(err); } const isServerSideRequest = !args.localParams; //TODO: add a rawSQL check for HTTP requests const showFullError = isServerSideRequest || args.type === "sql" || args.localParams?.clientReq?.socket?.prostgles?.get(args.prostgles.appId)?.rawSQL; if (showFullError) { return err; } const { view, allowedKeys } = args; const finalKeys = [...otherKeys, ...(allowedKeys ?? [])]; const errObject = pickKeys(err, finalKeys); if (view?.dboBuilder?.constraints && errObject.constraint && !errObject.column) { const constraint = view.dboBuilder.constraints.find( (c) => c.conname === errObject.constraint && c.relname === view.name, ); if (constraint) { const cols = view.columns?.filter( (c) => (!allowedKeys || allowedKeys.includes(c.name)) && constraint.conkey.includes(c.ordinal_position), ); const [firstCol] = cols ?? []; if (firstCol) { errObject.column = firstCol.name; errObject.columns = cols?.map((c) => c.name); } } } return errObject; } export function getClientErrorFromPGError( rawError: any, args: GetSerializedClientErrorFromPGErrorArgs, ) { const errorObj = getSerializedClientErrorFromPGError(rawError, args); return Promise.reject(errorObj); } export type PGConstraint = { /** * Constraint type */ contype: | "u" // Unique | "p" // Primary key | "c"; // Check /** * Column ordinal positions */ conkey: number[]; /** * Constraint name */ conname: string; /** * Table name */ relname: string; }; export const getConstraints = async ( db: DB, schema: ProstglesInitOptions["schemaFilter"], ): Promise => { const { sql, schemaNames } = getSchemaFilter(schema); return db.any( ` SELECT rel.relname, con.conkey, con.conname, con.contype FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace WHERE nsp.nspname ${sql} `, { schemaNames }, ); }; export const prepareOrderByQuery = (items: SortItem[], tableAlias?: string): string[] => { if (!items.length) return []; return [ "ORDER BY " + items .map((d) => { const orderType = d.asc ? " ASC " : " DESC "; const nullOrder = d.nulls ? ` NULLS ${d.nulls === "first" ? " FIRST " : " LAST "}` : ""; if (d.type === "query" && d.nested) { return d.fieldQuery; } return `${asNameAlias(d.key, tableAlias)} ${orderType} ${nullOrder}`; }) .join(", "), ]; }; export const getCanExecute = async (db: DB) => { try { await db.task((t) => t.any(`DO $$ BEGIN EXECUTE 'select 1'; END $$;`)); return true; } catch (error) { console.warn(error); } return false; }; export const withUserRLS = (localParams: LocalParams | undefined, query: string) => { const user = localParams?.isRemoteRequest?.user; const queryPrefix = `SET SESSION "prostgles.user" \nTO`; let firstQuery = `${queryPrefix} '';`; if (user) { firstQuery = pgp.as.format(`${queryPrefix} \${user};`, { user }); } return [firstQuery, query].join("\n"); };