mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Chore/shift manual queries into pg meta 03 (#43951)
## Context Shifting more dashboard queries into pg-meta so that we centralize all manually written queries in one place Having them in packages/pg-meta also allows us to write tests for them ## To test Just needs a smoke test on - Table Editor - Fetching entities - Viewing definition - SQL Editor - View ongoing queries - Abort queries - Integrations - Queues - Database - Migrations -Triggers (Updating)
This commit is contained in:
1 parent
3ae9f2d663
commit
be26feb9ba
53 files changed
+1537
-1578
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { THRESHOLD_COUNT } from '@supabase/pg-meta/src/sql/studio/get-count-estimate'
|
||||
import { THRESHOLD_COUNT } from '@supabase/pg-meta'
|
||||
import { keepPreviousData } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { useTableFilter } from 'components/grid/hooks/useTableFilter'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { THRESHOLD_COUNT } from '@supabase/pg-meta/src/query/table-row-query'
|
||||
import { THRESHOLD_COUNT } from '@supabase/pg-meta'
|
||||
import { keepPreviousData } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { useTableFilter } from 'components/grid/hooks/useTableFilter'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OptimizedSearchColumns } from '@supabase/pg-meta'
|
||||
import { THRESHOLD_COUNT } from '@supabase/pg-meta/src/sql/studio/get-count-estimate'
|
||||
import { THRESHOLD_COUNT, type OptimizedSearchColumns } from '@supabase/pg-meta'
|
||||
import { keepPreviousData } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { formatEstimatedCount } from 'components/grid/components/footer/pagination/Pagination.utils'
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { QUEUES_SCHEMA } from '@supabase/pg-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { DocsButton } from 'components/ui/DocsButton'
|
||||
import { FormHeader } from 'components/ui/Forms/FormHeader'
|
||||
import {
|
||||
@@ -16,16 +12,16 @@ import { InlineLink } from 'components/ui/InlineLink'
|
||||
import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query'
|
||||
import { useProjectPostgrestConfigUpdateMutation } from 'data/config/project-postgrest-config-update-mutation'
|
||||
import { useQueuesExposePostgrestStatusQuery } from 'data/database-queues/database-queues-expose-postgrest-status-query'
|
||||
import {
|
||||
QUEUES_SCHEMA,
|
||||
useDatabaseQueueToggleExposeMutation,
|
||||
} from 'data/database-queues/database-queues-toggle-postgrest-mutation'
|
||||
import { useDatabaseQueueToggleExposeMutation } from 'data/database-queues/database-queues-toggle-postgrest-mutation'
|
||||
import { useDatabaseQueuesVersionQuery } from 'data/database-queues/database-queues-version-query'
|
||||
import { useTableUpdateMutation } from 'data/tables/table-update-mutation'
|
||||
import { useTablesQuery } from 'data/tables/tables-query'
|
||||
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { DOCS_URL, IS_PLATFORM } from 'lib/constants'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Button,
|
||||
Form_Shadcn_,
|
||||
@@ -37,6 +33,7 @@ import {
|
||||
import { Admonition } from 'ui-patterns'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import { z } from 'zod'
|
||||
|
||||
// [Joshen] Not convinced with the UI and layout but getting the functionality out first
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { getQueuesExposePostgrestStatusSQL } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import minify from 'pg-minify'
|
||||
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError, UseCustomQueryOptions } from 'types'
|
||||
import { QUEUES_SCHEMA } from './database-queues-toggle-postgrest-mutation'
|
||||
|
||||
import { databaseQueuesKeys } from './keys'
|
||||
|
||||
export type DatabaseQueuesVariables = {
|
||||
@@ -11,21 +10,17 @@ export type DatabaseQueuesVariables = {
|
||||
connectionString?: string | null
|
||||
}
|
||||
|
||||
// [Joshen] Check if all the relevant functions exist to indicate whether PGMQ has been exposed through PostgREST
|
||||
const queueSqlQuery = minify(/**SQL */ `
|
||||
SELECT exists (select schema_name FROM information_schema.schemata WHERE schema_name = '${QUEUES_SCHEMA}');
|
||||
`)
|
||||
|
||||
export async function getDatabaseQueuesExposePostgrestStatus({
|
||||
projectRef,
|
||||
connectionString,
|
||||
}: DatabaseQueuesVariables) {
|
||||
if (!projectRef) throw new Error('Project ref is required')
|
||||
|
||||
const sql = getQueuesExposePostgrestStatusSQL()
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: queueSqlQuery,
|
||||
sql,
|
||||
})
|
||||
return result[0].exists as boolean
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getExposeQueuesSQL, HIDE_QUEUES_FROM_POSTGREST_SQL } from '@supabase/pg-meta'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import minify from 'pg-minify'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { databaseKeys } from 'data/database/keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { isGreaterThanOrEqual } from 'lib/semver'
|
||||
import { toast } from 'sonner'
|
||||
import type { ResponseError, UseCustomMutationOptions } from 'types'
|
||||
|
||||
import { databaseQueuesKeys } from './keys'
|
||||
|
||||
export type DatabaseQueueExposePostgrestVariables = {
|
||||
@@ -17,221 +17,6 @@ export type DatabaseQueueExposePostgrestVariables = {
|
||||
}
|
||||
|
||||
const CONDITIONAL_READ_SIGNATURE_PGMQ_VERSION = '1.5.0'
|
||||
export const QUEUES_SCHEMA = 'pgmq_public'
|
||||
|
||||
const getExposeQueuesSQL = (pgmqVersion: string) => {
|
||||
const conditionalJsonb = isGreaterThanOrEqual(
|
||||
pgmqVersion,
|
||||
CONDITIONAL_READ_SIGNATURE_PGMQ_VERSION
|
||||
)
|
||||
? `, conditional := '{}'::jsonb`
|
||||
: ''
|
||||
const jsonBArg = isGreaterThanOrEqual(pgmqVersion, CONDITIONAL_READ_SIGNATURE_PGMQ_VERSION)
|
||||
? `, jsonb`
|
||||
: ''
|
||||
|
||||
return minify(/* SQL */ `
|
||||
create schema if not exists ${QUEUES_SCHEMA};
|
||||
grant usage on schema ${QUEUES_SCHEMA} to postgres, anon, authenticated, service_role;
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.pop(
|
||||
queue_name text
|
||||
)
|
||||
returns setof pgmq.message_record
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select *
|
||||
from pgmq.pop(
|
||||
queue_name := queue_name
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.pop(queue_name text) is 'Retrieves and locks the next message from the specified queue.';
|
||||
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.send(
|
||||
queue_name text,
|
||||
message jsonb,
|
||||
sleep_seconds integer default 0 -- renamed from 'delay'
|
||||
)
|
||||
returns setof bigint
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select *
|
||||
from pgmq.send(
|
||||
queue_name := queue_name,
|
||||
msg := message,
|
||||
delay := sleep_seconds
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.send(queue_name text, message jsonb, sleep_seconds integer) is 'Sends a message to the specified queue, optionally delaying its availability by a number of seconds.';
|
||||
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.send_batch(
|
||||
queue_name text,
|
||||
messages jsonb[],
|
||||
sleep_seconds integer default 0 -- renamed from 'delay'
|
||||
)
|
||||
returns setof bigint
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select *
|
||||
from pgmq.send_batch(
|
||||
queue_name := queue_name,
|
||||
msgs := messages,
|
||||
delay := sleep_seconds
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.send_batch(queue_name text, messages jsonb[], sleep_seconds integer) is 'Sends a batch of messages to the specified queue, optionally delaying their availability by a number of seconds.';
|
||||
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.archive(
|
||||
queue_name text,
|
||||
message_id bigint
|
||||
)
|
||||
returns boolean
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return
|
||||
pgmq.archive(
|
||||
queue_name := queue_name,
|
||||
msg_id := message_id
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.archive(queue_name text, message_id bigint) is 'Archives a message by moving it from the queue to a permanent archive.';
|
||||
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.delete(
|
||||
queue_name text,
|
||||
message_id bigint
|
||||
)
|
||||
returns boolean
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return
|
||||
pgmq.delete(
|
||||
queue_name := queue_name,
|
||||
msg_id := message_id
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.delete(queue_name text, message_id bigint) is 'Permanently deletes a message from the specified queue.';
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.read(
|
||||
queue_name text,
|
||||
sleep_seconds integer,
|
||||
n integer
|
||||
)
|
||||
returns setof pgmq.message_record
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select *
|
||||
from pgmq.read(
|
||||
queue_name := queue_name,
|
||||
vt := sleep_seconds,
|
||||
qty := n ${conditionalJsonb}
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.read(queue_name text, sleep_seconds integer, n integer) is 'Reads up to "n" messages from the specified queue with an optional "sleep_seconds" (visibility timeout).';
|
||||
|
||||
-- Grant execute permissions on wrapper functions to roles
|
||||
grant execute on function ${QUEUES_SCHEMA}.pop(text) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.pop(text) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.send(text, jsonb, integer) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.send(text, jsonb, integer) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.send_batch(text, jsonb[], integer) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.send_batch(text, jsonb[], integer) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.archive(text, bigint) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.archive(text, bigint) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.delete(text, bigint) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.delete(text, bigint) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.read(text, integer, integer) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.read(text, integer, integer ${jsonBArg}) to postgres, service_role, anon, authenticated;
|
||||
|
||||
-- For the service role, we want full access
|
||||
-- Grant permissions on existing tables
|
||||
grant all privileges on all tables in schema pgmq to postgres, service_role;
|
||||
|
||||
-- Ensure service_role has permissions on future tables
|
||||
alter default privileges in schema pgmq grant all privileges on tables to postgres, service_role;
|
||||
|
||||
grant usage on schema pgmq to postgres, anon, authenticated, service_role;
|
||||
|
||||
|
||||
/*
|
||||
Grant access to sequences to API roles by default. Existing table permissions
|
||||
continue to enforce insert restrictions. This is necessary to accommodate the
|
||||
on-backup hook that rebuild queue table primary keys to avoid a pg_dump segfault.
|
||||
This can be removed once logical backups are completely retired.
|
||||
*/
|
||||
grant usage, select, update
|
||||
on all sequences in schema pgmq
|
||||
to anon, authenticated, service_role;
|
||||
|
||||
alter default privileges in schema pgmq
|
||||
grant usage, select, update
|
||||
on sequences
|
||||
to anon, authenticated, service_role;
|
||||
`)
|
||||
}
|
||||
|
||||
const HIDE_QUEUES_FROM_POSTGREST_SQL = minify(/* SQL */ `
|
||||
drop function if exists
|
||||
${QUEUES_SCHEMA}.pop(queue_name text),
|
||||
${QUEUES_SCHEMA}.send(queue_name text, message jsonb, sleep_seconds integer),
|
||||
${QUEUES_SCHEMA}.send_batch(queue_name text, message jsonb[], sleep_seconds integer),
|
||||
${QUEUES_SCHEMA}.archive(queue_name text, message_id bigint),
|
||||
${QUEUES_SCHEMA}.delete(queue_name text, message_id bigint),
|
||||
${QUEUES_SCHEMA}.read(queue_name text, sleep integer, n integer)
|
||||
;
|
||||
|
||||
-- Revoke execute permissions on inner pgmq functions to roles (inverse of enabling)
|
||||
do $$
|
||||
begin
|
||||
if exists (select 1 from pg_namespace where nspname = 'pgmq') then
|
||||
-- Revoke privileges on the schema itself
|
||||
revoke all on schema pgmq from anon, authenticated, service_role;
|
||||
|
||||
-- Revoke default privileges for future objects
|
||||
alter default privileges in schema pgmq revoke all on tables from anon, authenticated, service_role;
|
||||
alter default privileges in schema pgmq revoke all on sequences from anon, authenticated, service_role;
|
||||
alter default privileges in schema pgmq revoke all on functions from anon, authenticated, service_role;
|
||||
end if;
|
||||
end $$;
|
||||
|
||||
drop schema if exists ${QUEUES_SCHEMA};
|
||||
`)
|
||||
|
||||
export async function toggleQueuesExposurePostgrest({
|
||||
projectRef,
|
||||
@@ -239,7 +24,11 @@ export async function toggleQueuesExposurePostgrest({
|
||||
connectionString,
|
||||
enable,
|
||||
}: DatabaseQueueExposePostgrestVariables) {
|
||||
const sql = enable ? getExposeQueuesSQL(pgmqVersion) : HIDE_QUEUES_FROM_POSTGREST_SQL
|
||||
const isNewerPgmqversion = isGreaterThanOrEqual(
|
||||
pgmqVersion,
|
||||
CONDITIONAL_READ_SIGNATURE_PGMQ_VERSION
|
||||
)
|
||||
const sql = enable ? getExposeQueuesSQL({ isNewerPgmqversion }) : HIDE_QUEUES_FROM_POSTGREST_SQL
|
||||
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { getDatabaseTriggerUpdateSQL } from '@supabase/pg-meta'
|
||||
import { PGTrigger, PGTriggerCreate } from '@supabase/pg-meta/src/pg-meta-triggers'
|
||||
import { PostgresTrigger } from '@supabase/postgres-meta'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { quoteLiteral } from 'lib/pg-format'
|
||||
import { toast } from 'sonner'
|
||||
import type { ResponseError, UseCustomMutationOptions } from 'types'
|
||||
|
||||
import { databaseTriggerKeys } from './keys'
|
||||
|
||||
// [Joshen] Writing this query within FE as the PATCH endpoint from pg-meta only supports updating
|
||||
@@ -19,22 +19,6 @@ export type DatabaseTriggerUpdateVariables = {
|
||||
updatedTrigger: PGTriggerCreate & Pick<PGTrigger, 'enabled_mode'>
|
||||
}
|
||||
|
||||
export function getDatabaseTriggerUpdateSQL({
|
||||
originalTrigger,
|
||||
updatedTrigger,
|
||||
}: Pick<DatabaseTriggerUpdateVariables, 'originalTrigger' | 'updatedTrigger'>) {
|
||||
const { name, activation, events, schema, table, function_schema, function_name, function_args } =
|
||||
updatedTrigger
|
||||
return /* SQL */ `
|
||||
BEGIN;
|
||||
DROP TRIGGER "${originalTrigger.name}" ON "${originalTrigger.schema}"."${originalTrigger.table}";
|
||||
CREATE TRIGGER "${name}" ${activation} ${events.join(' OR ')} ON "${schema}"."${table}"
|
||||
FOR EACH ROW EXECUTE FUNCTION
|
||||
"${function_schema}"."${function_name}"(${function_args?.map(quoteLiteral).join(',') ?? ''});
|
||||
COMMIT;
|
||||
`.trim()
|
||||
}
|
||||
|
||||
export async function updateDatabaseTrigger({
|
||||
projectRef,
|
||||
connectionString,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { getTableConstraintsSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
type GetTableConstraintsVariables = {
|
||||
id?: number
|
||||
@@ -22,34 +24,6 @@ export enum CONSTRAINT_TYPE {
|
||||
EXCLUSION_CONSTRAINT = 'x',
|
||||
}
|
||||
|
||||
export const getTableConstraintsSql = ({ id }: GetTableConstraintsVariables) => {
|
||||
const sql = /* SQL */ `
|
||||
with table_info as (
|
||||
select
|
||||
n.nspname::text as schema,
|
||||
c.relname::text as name,
|
||||
to_regclass(concat('"', n.nspname, '"."', c.relname, '"')) as regclass
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.oid = ${id}
|
||||
)
|
||||
select
|
||||
con.oid as id,
|
||||
con.conname as name,
|
||||
con.contype as type
|
||||
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
|
||||
inner join table_info ti
|
||||
on ti.schema = nsp.nspname
|
||||
and ti.name = rel.relname;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type TableConstraintsVariables = GetTableConstraintsVariables & {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
@@ -62,6 +36,8 @@ export async function getTableConstraints(
|
||||
{ projectRef, connectionString, id }: TableConstraintsVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!id) throw new Error('Table ID is required')
|
||||
|
||||
const sql = getTableConstraintsSql({ id })
|
||||
const { result } = await executeSql(
|
||||
{ projectRef, connectionString, sql, queryKey: ['table-constraints', id] },
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { getDatabaseSizeSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
export const getDatabaseSizeSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
select sum(pg_database_size(pg_database.datname))::bigint as db_size from pg_database;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
export type DatabaseSizeVariables = {
|
||||
projectRef?: string
|
||||
|
||||
@@ -1,708 +0,0 @@
|
||||
import minify from 'pg-minify'
|
||||
|
||||
// Derived from https://github.com/MichaelDBA/pg_get_tabledef
|
||||
// NOTE: when updating, \n must be replaced with \\n in the SQL below
|
||||
// public.x is replaced with pg_temp.x
|
||||
|
||||
// REMOVE (as it breaks the encoding for some reason):
|
||||
// SELECT REPLACE(REPLACE(setting, '"$user"', '$user'), '$user', '"$user"') INTO search_path_old
|
||||
// FROM pg_settings
|
||||
// WHERE name = 'search_path';
|
||||
|
||||
export const CREATE_PG_GET_TABLEDEF_SQL = minify(
|
||||
/* SQL */ `
|
||||
DROP TYPE IF EXISTS pg_temp.tabledefs CASCADE;
|
||||
CREATE TYPE pg_temp.tabledefs AS ENUM ('PKEY_INTERNAL','PKEY_EXTERNAL','FKEYS_INTERNAL', 'FKEYS_EXTERNAL', 'COMMENTS', 'FKEYS_NONE', 'INCLUDE_TRIGGERS', 'NO_TRIGGERS');
|
||||
|
||||
-- SELECT * FROM pg_temp.pg_get_coldef('sample','orders','id');
|
||||
-- DROP FUNCTION pg_temp.pg_get_coldef(text,text,text,boolean);
|
||||
CREATE OR REPLACE FUNCTION pg_temp.pg_get_coldef(
|
||||
in_schema text,
|
||||
in_table text,
|
||||
in_column text,
|
||||
oldway boolean default False
|
||||
)
|
||||
RETURNS text
|
||||
LANGUAGE plpgsql VOLATILE
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
v_coldef text;
|
||||
v_dt1 text;
|
||||
v_dt2 text;
|
||||
v_dt3 text;
|
||||
v_nullable boolean;
|
||||
v_position int;
|
||||
v_identity text;
|
||||
v_generated text;
|
||||
v_hasdflt boolean;
|
||||
v_dfltexpr text;
|
||||
|
||||
BEGIN
|
||||
IF oldway THEN
|
||||
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) INTO v_coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t
|
||||
WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum;
|
||||
-- RAISE NOTICE 'DEBUG: oldway=%',v_coldef;
|
||||
ELSE
|
||||
-- a.attrelid::regclass::text, a.attname
|
||||
SELECT CASE WHEN a.atttypid = ANY ('{int,int8,int2}'::regtype[]) AND EXISTS (SELECT FROM pg_attrdef ad WHERE ad.adrelid = a.attrelid AND ad.adnum = a.attnum AND
|
||||
pg_get_expr(ad.adbin, ad.adrelid) = 'nextval(''' || (pg_get_serial_sequence (a.attrelid::regclass::text, a.attname))::regclass || '''::regclass)') THEN CASE a.atttypid
|
||||
WHEN 'int'::regtype THEN 'serial' WHEN 'int8'::regtype THEN 'bigserial' WHEN 'int2'::regtype THEN 'smallserial' END ELSE format_type(a.atttypid, a.atttypmod) END AS data_type
|
||||
INTO v_coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t
|
||||
WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum;
|
||||
-- RAISE NOTICE 'DEBUG: newway=%',v_coldef;
|
||||
|
||||
-- Issue#24: not implemented yet
|
||||
-- might replace with this below to do more detailed parsing...
|
||||
-- SELECT a.atttypid::regtype AS dt1, format_type(a.atttypid, a.atttypmod) as dt2, t.typname as dt3, CASE WHEN not(a.attnotnull) THEN True ELSE False END AS nullable,
|
||||
-- a.attnum, a.attidentity, a.attgenerated, a.atthasdef, pg_get_expr(ad.adbin, ad.adrelid) dfltexpr
|
||||
-- INTO v_dt1, v_dt2, v_dt3, v_nullable, v_position, v_identity, v_generated, v_hasdflt, v_dfltexpr
|
||||
-- FROM pg_attribute a JOIN pg_class c ON (a.attrelid = c.oid) JOIN pg_type t ON (a.atttypid = t.oid) LEFT JOIN pg_attrdef ad ON (a.attrelid = ad.adrelid AND a.attnum = ad.adnum)
|
||||
-- WHERE c.relkind in ('r','p') AND a.attnum > 0 AND NOT a.attisdropped AND c.relnamespace::regnamespace::text = in_schema AND c.relname = in_table AND a.attname = in_column;
|
||||
-- RAISE NOTICE 'schema=% table=% column=% dt1=% dt2=% dt3=% nullable=% pos=% identity=% generated=% HasDefault=% DeftExpr=%', in_schema, in_table, in_column, v_dt1,v_dt2,v_dt3,v_nullable,v_position,v_identity,v_generated,v_hasdflt,v_dfltexpr;
|
||||
END IF;
|
||||
RETURN v_coldef;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- SELECT * FROM pg_temp.pg_get_tabledef('sample', 'address', false);
|
||||
DROP FUNCTION IF EXISTS pg_temp.pg_get_tabledef(character varying,character varying,boolean,tabledefs[]);
|
||||
CREATE OR REPLACE FUNCTION pg_temp.pg_get_tabledef(
|
||||
in_schema varchar,
|
||||
in_table varchar,
|
||||
_verbose boolean,
|
||||
VARIADIC arr pg_temp.tabledefs[] DEFAULT '{}':: pg_temp.tabledefs[]
|
||||
)
|
||||
RETURNS text
|
||||
LANGUAGE plpgsql VOLATILE
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
v_qualified text := '';
|
||||
v_table_ddl text;
|
||||
v_table_oid int;
|
||||
v_colrec record;
|
||||
v_constraintrec record;
|
||||
v_trigrec record;
|
||||
v_indexrec record;
|
||||
v_rec record;
|
||||
v_constraint_name text;
|
||||
v_constraint_def text;
|
||||
v_pkey_def text := '';
|
||||
v_fkey_def text := '';
|
||||
v_fkey_defs text := '';
|
||||
v_trigger text := '';
|
||||
v_partition_key text := '';
|
||||
v_partbound text;
|
||||
v_parent text;
|
||||
v_parent_schema text;
|
||||
v_persist text;
|
||||
v_temp text := '';
|
||||
v_temp2 text;
|
||||
v_relopts text;
|
||||
v_tablespace text;
|
||||
v_pgversion int;
|
||||
bSerial boolean;
|
||||
bPartition boolean;
|
||||
bInheritance boolean;
|
||||
bRelispartition boolean;
|
||||
constraintarr text[] := '{}';
|
||||
constraintelement text;
|
||||
bSkip boolean;
|
||||
bVerbose boolean := False;
|
||||
v_cnt1 integer;
|
||||
v_cnt2 integer;
|
||||
search_path_old text := '';
|
||||
search_path_new text := '';
|
||||
v_partial boolean;
|
||||
v_pos integer;
|
||||
|
||||
-- assume defaults for ENUMs at the getgo
|
||||
pkcnt int := 0;
|
||||
fkcnt int := 0;
|
||||
trigcnt int := 0;
|
||||
cmtcnt int := 0;
|
||||
pktype pg_temp.tabledefs := 'PKEY_INTERNAL';
|
||||
fktype pg_temp.tabledefs := 'FKEYS_INTERNAL';
|
||||
trigtype pg_temp.tabledefs := 'NO_TRIGGERS';
|
||||
arglen integer;
|
||||
vargs text;
|
||||
avarg pg_temp.tabledefs;
|
||||
|
||||
-- exception variables
|
||||
v_ret text;
|
||||
v_diag1 text;
|
||||
v_diag2 text;
|
||||
v_diag3 text;
|
||||
v_diag4 text;
|
||||
v_diag5 text;
|
||||
v_diag6 text;
|
||||
|
||||
BEGIN
|
||||
SET client_min_messages = 'notice';
|
||||
IF _verbose THEN bVerbose = True; END IF;
|
||||
|
||||
-- v17 fix: handle case-sensitive
|
||||
-- v_qualified = in_schema || '.' || in_table;
|
||||
|
||||
arglen := array_length($4, 1);
|
||||
IF arglen IS NULL THEN
|
||||
-- nothing to do, so assume defaults
|
||||
NULL;
|
||||
ELSE
|
||||
-- loop thru args
|
||||
-- IF 'NO_TRIGGERS' = ANY ($4)
|
||||
-- select array_to_string($4, ',', '***') INTO vargs;
|
||||
IF bVerbose THEN RAISE NOTICE 'arguments=%', $4; END IF;
|
||||
FOREACH avarg IN ARRAY $4 LOOP
|
||||
IF bVerbose THEN RAISE NOTICE 'arg=%', avarg; END IF;
|
||||
IF avarg = 'FKEYS_INTERNAL' OR avarg = 'FKEYS_EXTERNAL' OR avarg = 'FKEYS_NONE' THEN
|
||||
fkcnt = fkcnt + 1;
|
||||
fktype = avarg;
|
||||
ELSEIF avarg = 'INCLUDE_TRIGGERS' OR avarg = 'NO_TRIGGERS' THEN
|
||||
trigcnt = trigcnt + 1;
|
||||
trigtype = avarg;
|
||||
ELSEIF avarg = 'PKEY_EXTERNAL' THEN
|
||||
pkcnt = pkcnt + 1;
|
||||
pktype = avarg;
|
||||
ELSEIF avarg = 'COMMENTS' THEN
|
||||
cmtcnt = cmtcnt + 1;
|
||||
|
||||
END IF;
|
||||
END LOOP;
|
||||
IF fkcnt > 1 THEN
|
||||
RAISE WARNING 'Only one foreign key option can be provided. You provided %', fkcnt;
|
||||
RETURN '';
|
||||
ELSEIF trigcnt > 1 THEN
|
||||
RAISE WARNING 'Only one trigger option can be provided. You provided %', trigcnt;
|
||||
RETURN '';
|
||||
ELSEIF pkcnt > 1 THEN
|
||||
RAISE WARNING 'Only one pkey option can be provided. You provided %', pkcnt;
|
||||
RETURN '';
|
||||
ELSEIF cmtcnt > 1 THEN
|
||||
RAISE WARNING 'Only one comments option can be provided. You provided %', cmtcnt;
|
||||
RETURN '';
|
||||
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
SELECT c.oid, (select setting from pg_settings where name = 'server_version_num') INTO v_table_oid, v_pgversion FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relkind in ('r','p') AND c.relname = in_table AND n.nspname = in_schema;
|
||||
|
||||
-- set search_path = public before we do anything to force explicit schema qualification but dont forget to set it back before exiting...
|
||||
SELECT setting INTO search_path_old FROM pg_settings WHERE name = 'search_path';
|
||||
|
||||
-- RAISE NOTICE 'DEBUG tableddl: saving old search_path: ***%***', search_path_old;
|
||||
EXECUTE 'SET search_path = "public"';
|
||||
SELECT setting INTO search_path_new FROM pg_settings WHERE name = 'search_path';
|
||||
-- RAISE NOTICE 'DEBUG tableddl: using new search path=***%***', search_path_new;
|
||||
|
||||
-- throw an error if table was not found
|
||||
IF (v_table_oid IS NULL) THEN
|
||||
RAISE EXCEPTION 'table does not exist';
|
||||
END IF;
|
||||
|
||||
-- get user-defined tablespaces if applicable
|
||||
SELECT tablespace INTO v_temp FROM pg_tables WHERE schemaname = in_schema and tablename = in_table and tablespace IS NOT NULL;
|
||||
IF v_temp IS NULL THEN
|
||||
v_tablespace := 'TABLESPACE pg_default';
|
||||
ELSE
|
||||
v_tablespace := 'TABLESPACE ' || v_temp;
|
||||
END IF;
|
||||
|
||||
-- also see if there are any SET commands for this table, ie, autovacuum_enabled=off, fillfactor=70
|
||||
WITH relopts AS (SELECT unnest(c.reloptions) relopts FROM pg_class c, pg_namespace n WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table)
|
||||
SELECT string_agg(r.relopts, ', ') as relopts INTO v_temp from relopts r;
|
||||
IF v_temp IS NULL THEN
|
||||
v_relopts := '';
|
||||
ELSE
|
||||
v_relopts := ' WITH (' || v_temp || ')';
|
||||
END IF;
|
||||
|
||||
-- -----------------------------------------------------------------------------------
|
||||
-- Create table defs for partitions/children using inheritance or declarative methods.
|
||||
-- inheritance: pg_class.relkind = 'r' pg_class.relispartition=false pg_class.relpartbound is NULL
|
||||
-- declarative: pg_class.relkind = 'r' pg_class.relispartition=true pg_class.relpartbound is NOT NULL
|
||||
-- -----------------------------------------------------------------------------------
|
||||
v_partbound := '';
|
||||
bPartition := False;
|
||||
bInheritance := False;
|
||||
IF v_pgversion < 100000 THEN
|
||||
-- Issue#11: handle parent schema
|
||||
SELECT c2.relname parent, c2.relnamespace::regnamespace INTO v_parent, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2
|
||||
WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r';
|
||||
IF (v_parent IS NOT NULL) THEN
|
||||
bPartition := True;
|
||||
bInheritance := True;
|
||||
END IF;
|
||||
ELSE
|
||||
-- Issue#11: handle parent schema
|
||||
SELECT c2.relname parent, c1.relispartition, pg_get_expr(c1.relpartbound, c1.oid, true), c2.relnamespace::regnamespace INTO v_parent, bRelispartition, v_partbound, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2
|
||||
WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r';
|
||||
IF (v_parent IS NOT NULL) THEN
|
||||
bPartition := True;
|
||||
IF bRelispartition THEN
|
||||
bInheritance := False;
|
||||
ELSE
|
||||
bInheritance := True;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
IF bPartition THEN
|
||||
--Issue#17 fix for case-sensitive tables
|
||||
-- SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s
|
||||
-- WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = quote_ident(in_schema) AND t.table_name = quote_ident(in_table) AND t.table_type = 'BASE TABLE');
|
||||
SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s
|
||||
WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = in_schema AND t.table_name = in_table AND t.table_type = 'BASE TABLE');
|
||||
|
||||
--Issue#19 put double-quotes around SQL keyword column names
|
||||
-- Issue#121: fix keyword lookup for table name not column name that does not apply here
|
||||
-- SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = v_colrec.column_name AND catcode = 'R';
|
||||
SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = in_table AND catcode = 'R';
|
||||
|
||||
IF bInheritance THEN
|
||||
-- inheritance-based
|
||||
IF v_cnt1 > 0 OR v_cnt2 > 0 THEN
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '"( '|| E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || '( '|| E'\\n';
|
||||
END IF;
|
||||
|
||||
-- Jump to constraints section to add the check constraints
|
||||
ELSE
|
||||
-- declarative-based
|
||||
IF v_relopts <> '' THEN
|
||||
IF v_cnt1 > 0 OR v_cnt2 > 0 THEN
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '" PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || v_relopts || ' ' || v_tablespace || '; ' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || v_relopts || ' ' || v_tablespace || '; ' || E'\\n';
|
||||
END IF;
|
||||
ELSE
|
||||
IF v_cnt1 > 0 OR v_cnt2 > 0 THEN
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '" PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || ' ' || v_tablespace || '; ' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || ' ' || v_tablespace || '; ' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
-- Jump to constraints and index section to add the check constraints and indexes and perhaps FKeys
|
||||
END IF;
|
||||
END IF;
|
||||
IF bVerbose THEN RAISE NOTICE '(1)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
IF NOT bPartition THEN
|
||||
-- see if this is unlogged or temporary table
|
||||
select c.relpersistence into v_persist from pg_class c, pg_namespace n where n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r';
|
||||
IF v_persist = 'u' THEN
|
||||
v_temp := 'UNLOGGED';
|
||||
ELSIF v_persist = 't' THEN
|
||||
v_temp := 'TEMPORARY';
|
||||
ELSE
|
||||
v_temp := '';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- start the create definition for regular tables unless we are in progress creating an inheritance-based child table
|
||||
IF NOT bPartition THEN
|
||||
--Issue#17 fix for case-sensitive tables
|
||||
-- SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s
|
||||
-- WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = quote_ident(in_schema) AND t.table_name = quote_ident(in_table) AND t.table_type = 'BASE TABLE');
|
||||
SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s
|
||||
WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = in_schema AND t.table_name = in_table AND t.table_type = 'BASE TABLE');
|
||||
IF v_cnt1 > 0 THEN
|
||||
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '."' || in_table || '" (' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '.' || in_table || ' (' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
-- RAISE NOTICE 'DEBUG2: tabledef so far: %', v_table_ddl;
|
||||
-- define all of the columns in the table unless we are in progress creating an inheritance-based child table
|
||||
IF NOT bPartition THEN
|
||||
FOR v_colrec IN
|
||||
SELECT c.column_name, c.data_type, c.udt_name, c.udt_schema, c.character_maximum_length, c.is_nullable, c.column_default, c.numeric_precision, c.numeric_scale, c.is_identity, c.identity_generation, c.is_generated, c.generation_expression
|
||||
FROM information_schema.columns c WHERE (table_schema, table_name) = (in_schema, in_table) ORDER BY ordinal_position
|
||||
LOOP
|
||||
IF bVerbose THEN RAISE NOTICE '(col loop) name=% type=% udt_name=% default=% is_generated=% gen_expr=%', v_colrec.column_name, v_colrec.data_type, v_colrec.udt_name, v_colrec.column_default, v_colrec.is_generated, v_colrec.generation_expression; END IF;
|
||||
|
||||
-- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling
|
||||
-- SELECT CASE WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL THEN True ELSE False END into bSerial;
|
||||
SELECT CASE WHEN pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL THEN True ELSE False END into bSerial;
|
||||
IF bVerbose THEN
|
||||
-- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling
|
||||
-- SELECT pg_get_serial_sequence(v_qualified, v_colrec.column_name) into v_temp;
|
||||
SELECT pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) into v_temp;
|
||||
IF v_temp IS NULL THEN v_temp = 'NA'; END IF;
|
||||
SELECT pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name) INTO v_diag1;
|
||||
RAISE NOTICE 'DEBUG table: % Column: % datatype: % Serial=% serialval=% coldef=%', v_qualified, v_colrec.column_name, v_colrec.data_type, bSerial, v_temp, v_diag1;
|
||||
RAISE NOTICE 'DEBUG tabledef: %', v_table_ddl;
|
||||
END IF;
|
||||
|
||||
--Issue#17 put double-quotes around case-sensitive column names
|
||||
SELECT COUNT(*) INTO v_cnt1 FROM information_schema.columns t WHERE EXISTS (SELECT REGEXP_MATCHES(s.column_name, '([A-Z]+)','g') FROM information_schema.columns s
|
||||
WHERE t.table_schema=s.table_schema and t.table_name=s.table_name and t.column_name=s.column_name AND t.table_schema = quote_ident(in_schema) AND column_name = v_colrec.column_name);
|
||||
|
||||
--Issue#19 put double-quotes around SQL keyword column names
|
||||
SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = v_colrec.column_name AND catcode = 'R';
|
||||
|
||||
IF v_cnt1 > 0 OR v_cnt2 > 0 THEN
|
||||
v_table_ddl := v_table_ddl || ' "' || v_colrec.column_name || '" ';
|
||||
ELSE
|
||||
v_table_ddl := v_table_ddl || ' ' || v_colrec.column_name || ' ';
|
||||
END IF;
|
||||
|
||||
-- Issue#23: Handle autogenerated columns and rewrite as a simpler IF THEN ELSE branch instead of a much more complex embedded CASE STATEMENT
|
||||
IF v_colrec.is_generated = 'ALWAYS' and v_colrec.generation_expression IS NOT NULL THEN
|
||||
-- searchable tsvector GENERATED ALWAYS AS (to_tsvector('simple'::regconfig, COALESCE(translate(email, '@.-'::citext, ' '::text), ''::text)) ) STORED
|
||||
v_temp = v_colrec.data_type || ' GENERATED ALWAYS AS (' || v_colrec.generation_expression || ') STORED ';
|
||||
ELSEIF v_colrec.udt_name in ('geometry', 'box2d', 'box2df', 'box3d', 'geography', 'geometry_dump', 'gidx', 'spheroid', 'valid_detail') THEN
|
||||
v_temp = v_colrec.udt_name;
|
||||
ELSEIF v_colrec.data_type = 'USER-DEFINED' THEN
|
||||
v_temp = v_colrec.udt_schema || '.' || v_colrec.udt_name;
|
||||
ELSEIF v_colrec.data_type = 'ARRAY' THEN
|
||||
-- Issue#6 fix: handle arrays
|
||||
v_temp = pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name);
|
||||
-- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling
|
||||
-- WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL
|
||||
ELSEIF pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL THEN
|
||||
-- Issue#8 fix: handle serial. Note: NOT NULL is implied so no need to declare it explicitly
|
||||
v_temp = pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name);
|
||||
ELSE
|
||||
v_temp = v_colrec.data_type;
|
||||
END IF;
|
||||
-- RAISE NOTICE 'column def1=%', v_temp;
|
||||
|
||||
-- handle IDENTITY columns
|
||||
IF v_colrec.is_identity = 'YES' THEN
|
||||
IF v_colrec.identity_generation = 'ALWAYS' THEN
|
||||
v_temp = v_temp || ' GENERATED ALWAYS AS IDENTITY';
|
||||
ELSE
|
||||
v_temp = v_temp || ' GENERATED BY DEFAULT AS IDENTITY';
|
||||
END IF;
|
||||
ELSEIF v_colrec.character_maximum_length IS NOT NULL THEN
|
||||
v_temp = v_temp || ('(' || v_colrec.character_maximum_length || ')');
|
||||
ELSEIF v_colrec.numeric_precision > 0 AND v_colrec.numeric_scale > 0 THEN
|
||||
v_temp = v_temp || '(' || v_colrec.numeric_precision || ',' || v_colrec.numeric_scale || ')';
|
||||
END IF;
|
||||
|
||||
-- Handle NULL/NOT NULL
|
||||
IF bSerial THEN
|
||||
v_temp = v_temp || ' NOT NULL';
|
||||
ELSEIF v_colrec.is_nullable = 'NO' THEN
|
||||
v_temp = v_temp || ' NOT NULL';
|
||||
ELSEIF v_colrec.is_nullable = 'YES' THEN
|
||||
v_temp = v_temp || ' NULL';
|
||||
END IF;
|
||||
|
||||
-- Handle defaults
|
||||
IF v_colrec.column_default IS NOT null AND NOT bSerial THEN
|
||||
-- RAISE NOTICE 'Setting default for column, %', v_colrec.column_name;
|
||||
v_temp = v_temp || (' DEFAULT ' || v_colrec.column_default);
|
||||
END IF;
|
||||
v_temp = v_temp || ',' || E'\\n';
|
||||
-- RAISE NOTICE 'column def2=%', v_temp;
|
||||
v_table_ddl := v_table_ddl || v_temp;
|
||||
-- RAISE NOTICE 'tabledef=%', v_table_ddl;
|
||||
|
||||
END LOOP;
|
||||
END IF;
|
||||
IF bVerbose THEN RAISE NOTICE '(2)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- define all the constraints: conparentid does not exist pre PGv11
|
||||
IF v_pgversion < 110000 THEN
|
||||
FOR v_constraintrec IN
|
||||
SELECT con.conname as constraint_name, con.contype as constraint_type,
|
||||
CASE
|
||||
WHEN con.contype = 'p' THEN 1 -- primary key constraint
|
||||
WHEN con.contype = 'u' THEN 2 -- unique constraint
|
||||
WHEN con.contype = 'f' THEN 3 -- foreign key constraint
|
||||
WHEN con.contype = 'c' THEN 4
|
||||
ELSE 5
|
||||
END as type_rank,
|
||||
pg_get_constraintdef(con.oid) as constraint_definition
|
||||
FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
|
||||
WHERE nsp.nspname = in_schema AND rel.relname = in_table ORDER BY type_rank
|
||||
LOOP
|
||||
v_constraint_name := v_constraintrec.constraint_name;
|
||||
v_constraint_def := v_constraintrec.constraint_definition;
|
||||
IF v_constraintrec.type_rank = 1 THEN
|
||||
IF pkcnt = 0 OR pktype = 'PKEY_INTERNAL' THEN
|
||||
-- internal def
|
||||
v_constraint_name := v_constraintrec.constraint_name;
|
||||
v_constraint_def := v_constraintrec.constraint_definition;
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
ELSE
|
||||
-- Issue#16 handle external PG def
|
||||
SELECT 'ALTER TABLE ONLY ' || in_schema || '.' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_pkey_def
|
||||
FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table and r.conname = v_constraint_name;
|
||||
END IF;
|
||||
IF bPartition THEN
|
||||
continue;
|
||||
END IF;
|
||||
ELSIF v_constraintrec.type_rank = 3 THEN
|
||||
-- handle foreign key constraints
|
||||
--Issue#22 fix: added FKEY_NONE check
|
||||
IF fktype = 'FKEYS_NONE' THEN
|
||||
-- skip
|
||||
continue;
|
||||
ELSIF fkcnt = 0 OR fktype = 'FKEYS_INTERNAL' THEN
|
||||
-- internal def
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
ELSE
|
||||
-- external def
|
||||
SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_fkey_def
|
||||
FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table;
|
||||
v_fkey_defs = v_fkey_defs || v_fkey_def || E'\\n';
|
||||
END IF;
|
||||
ELSE
|
||||
-- handle all other constraints besides PKEY and FKEYS as internal defs by default
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
END IF;
|
||||
if bVerbose THEN RAISE NOTICE 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF;
|
||||
constraintarr := constraintarr || v_constraintrec.constraint_name:: text;
|
||||
|
||||
END LOOP;
|
||||
ELSE
|
||||
-- handle PG versions 11 and up
|
||||
-- Issue#20: Fix logic for external PKEY and FKEYS
|
||||
FOR v_constraintrec IN
|
||||
SELECT con.conname as constraint_name, con.contype as constraint_type,
|
||||
CASE
|
||||
WHEN con.contype = 'p' THEN 1 -- primary key constraint
|
||||
WHEN con.contype = 'u' THEN 2 -- unique constraint
|
||||
WHEN con.contype = 'f' THEN 3 -- foreign key constraint
|
||||
WHEN con.contype = 'c' THEN 4
|
||||
ELSE 5
|
||||
END as type_rank,
|
||||
pg_get_constraintdef(con.oid) as constraint_definition
|
||||
FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
|
||||
WHERE nsp.nspname = in_schema AND rel.relname = in_table
|
||||
--Issue#13 added this condition:
|
||||
AND con.conparentid = 0
|
||||
ORDER BY type_rank
|
||||
LOOP
|
||||
v_constraint_name := v_constraintrec.constraint_name;
|
||||
v_constraint_def := v_constraintrec.constraint_definition;
|
||||
IF v_constraintrec.type_rank = 1 THEN
|
||||
IF pkcnt = 0 OR pktype = 'PKEY_INTERNAL' THEN
|
||||
-- internal def
|
||||
v_constraint_name := v_constraintrec.constraint_name;
|
||||
v_constraint_def := v_constraintrec.constraint_definition;
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
ELSE
|
||||
-- Issue#16 handle external PG def
|
||||
SELECT 'ALTER TABLE ONLY ' || in_schema || '.' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_pkey_def
|
||||
FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table;
|
||||
END IF;
|
||||
IF bPartition THEN
|
||||
continue;
|
||||
END IF;
|
||||
ELSIF v_constraintrec.type_rank = 3 THEN
|
||||
-- handle foreign key constraints
|
||||
--Issue#22 fix: added FKEY_NONE check
|
||||
IF fktype = 'FKEYS_NONE' THEN
|
||||
-- skip
|
||||
continue;
|
||||
ELSIF fkcnt = 0 OR fktype = 'FKEYS_INTERNAL' THEN
|
||||
-- internal def
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
ELSE
|
||||
-- external def
|
||||
SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_fkey_def
|
||||
FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table and
|
||||
r.conname = v_constraint_name and r.conparentid = 0;
|
||||
v_fkey_defs = v_fkey_defs || v_fkey_def || E'\\n';
|
||||
END IF;
|
||||
ELSE
|
||||
-- handle all other constraints besides PKEY and FKEYS as internal defs by default
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
END IF;
|
||||
if bVerbose THEN RAISE NOTICE 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF;
|
||||
constraintarr := constraintarr || v_constraintrec.constraint_name:: text;
|
||||
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- drop the last comma before ending the create statement, which should be right before the carriage return character
|
||||
-- Issue#24: make sure the comma is there before removing it
|
||||
select substring(v_table_ddl, length(v_table_ddl) - 1, 1) INTO v_temp;
|
||||
IF v_temp = ',' THEN
|
||||
v_table_ddl = substr(v_table_ddl, 0, length(v_table_ddl) - 1) || E'\\n';
|
||||
END IF;
|
||||
IF bVerbose THEN RAISE NOTICE '(3)tabledef so far: %', trim(v_table_ddl); END IF;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- at this point we have everything up to the last table-enclosing parenthesis
|
||||
-- ---------------------------------------------------------------------------
|
||||
IF bVerbose THEN RAISE NOTICE '(4)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- See if this is an inheritance-based child table and finish up the table create.
|
||||
IF bPartition and bInheritance THEN
|
||||
-- Issue#11: handle parent schema
|
||||
-- v_table_ddl := v_table_ddl || ') INHERITS (' || in_schema || '.' || v_parent || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
|
||||
IF v_parent_schema = '' OR v_parent_schema IS NULL THEN v_parent_schema = in_schema; END IF;
|
||||
v_table_ddl := v_table_ddl || ') INHERITS (' || v_parent_schema || '.' || v_parent || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
|
||||
END IF;
|
||||
|
||||
IF v_pgversion >= 100000 AND NOT bPartition and NOT bInheritance THEN
|
||||
-- See if this is a partitioned table (pg_class.relkind = 'p') and add the partitioned key
|
||||
SELECT pg_get_partkeydef(c1.oid) as partition_key INTO v_partition_key FROM pg_class c1 JOIN pg_namespace n ON (n.oid = c1.relnamespace) LEFT JOIN pg_partitioned_table p ON (c1.oid = p.partrelid)
|
||||
WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.relkind = 'p';
|
||||
|
||||
IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN
|
||||
-- add partition clause
|
||||
-- NOTE: cannot specify default tablespace for partitioned relations
|
||||
-- v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ' ' || v_tablespace || ';' || E'\\n';
|
||||
v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ';' || E'\\n';
|
||||
ELSEIF v_relopts <> '' THEN
|
||||
v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
|
||||
ELSE
|
||||
-- end the create definition
|
||||
v_table_ddl := v_table_ddl || ') ' || v_tablespace || ';' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF bVerbose THEN RAISE NOTICE '(5)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- Add closing paren for regular tables
|
||||
-- IF NOT bPartition THEN
|
||||
-- v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || E';\\n';
|
||||
-- END IF;
|
||||
-- RAISE NOTICE 'ddlsofar3: %', v_table_ddl;
|
||||
|
||||
-- Issue#16 create the external PKEY def if indicated
|
||||
IF v_pkey_def <> '' THEN
|
||||
v_table_ddl := v_table_ddl || v_pkey_def || E'\\n';
|
||||
END IF;
|
||||
|
||||
-- Issue#20
|
||||
IF v_fkey_defs <> '' THEN
|
||||
v_table_ddl := v_table_ddl || v_fkey_defs || E'\\n';
|
||||
END IF;
|
||||
|
||||
IF bVerbose THEN RAISE NOTICE '(6)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- create indexes
|
||||
FOR v_indexrec IN
|
||||
SELECT indexdef, COALESCE(tablespace, 'pg_default') as tablespace, indexname FROM pg_indexes WHERE (schemaname, tablename) = (in_schema, in_table)
|
||||
LOOP
|
||||
-- RAISE NOTICE 'DEBUG6: indexname=% indexdef=%', v_indexrec.indexname, v_indexrec.indexdef;
|
||||
-- loop through constraints and skip ones already defined
|
||||
bSkip = False;
|
||||
FOREACH constraintelement IN ARRAY constraintarr
|
||||
LOOP
|
||||
IF constraintelement = v_indexrec.indexname THEN
|
||||
-- RAISE NOTICE 'DEBUG7: skipping index, %', v_indexrec.indexname;
|
||||
bSkip = True;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
if bSkip THEN CONTINUE; END IF;
|
||||
|
||||
-- Add IF NOT EXISTS clause so partition index additions will not be created if declarative partition in effect and index already created on parent
|
||||
v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE INDEX', 'CREATE INDEX IF NOT EXISTS');
|
||||
-- Fix Issue#26: do it for unique/primary key indexes as well
|
||||
v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE UNIQUE INDEX', 'CREATE UNIQUE INDEX IF NOT EXISTS');
|
||||
-- RAISE NOTICE 'DEBUG8: adding index, %', v_indexrec.indexname;
|
||||
|
||||
-- NOTE: cannot specify default tablespace for partitioned relations
|
||||
IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN
|
||||
v_table_ddl := v_table_ddl || v_indexrec.indexdef || ';' || E'\\n';
|
||||
ELSE
|
||||
-- Issue#25: see if partial index or not
|
||||
select CASE WHEN i.indpred IS NOT NULL THEN True ELSE False END INTO v_partial
|
||||
FROM pg_index i JOIN pg_class c1 ON (i.indexrelid = c1.oid) JOIN pg_class c2 ON (i.indrelid = c2.oid)
|
||||
WHERE c1.relnamespace::regnamespace::text = in_schema AND c2.relnamespace::regnamespace::text = in_schema AND c2.relname = in_table AND c1.relname = v_indexrec.indexname;
|
||||
IF v_partial THEN
|
||||
-- Put tablespace def before WHERE CLAUSE
|
||||
v_temp = v_indexrec.indexdef;
|
||||
v_pos = POSITION(' WHERE ' IN v_temp);
|
||||
v_temp2 = SUBSTRING(v_temp, v_pos);
|
||||
v_temp = SUBSTRING(v_temp, 1, v_pos);
|
||||
v_table_ddl := v_table_ddl || v_temp || ' TABLESPACE ' || v_indexrec.tablespace || v_temp2 || ';' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := v_table_ddl || v_indexrec.indexdef || ' TABLESPACE ' || v_indexrec.tablespace || ';' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
END LOOP;
|
||||
IF bVerbose THEN RAISE NOTICE '(7)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- Issue#20: added logic for table and column comments
|
||||
IF cmtcnt > 0 THEN
|
||||
FOR v_rec IN
|
||||
SELECT c.relname, 'COMMENT ON ' || CASE WHEN c.relkind in ('r','p') AND a.attname IS NULL THEN 'TABLE ' WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN 'COLUMN ' WHEN c.relkind = 'f' THEN 'FOREIGN TABLE '
|
||||
WHEN c.relkind = 'm' THEN 'MATERIALIZED VIEW ' WHEN c.relkind = 'v' THEN 'VIEW ' WHEN c.relkind = 'i' THEN 'INDEX ' WHEN c.relkind = 'S' THEN 'SEQUENCE ' ELSE 'XX' END || n.nspname || '.' ||
|
||||
CASE WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN quote_ident(c.relname) || '.' || a.attname ELSE quote_ident(c.relname) END || ' IS ' || quote_literal(d.description) || ';' as ddl
|
||||
FROM pg_class c JOIN pg_namespace n ON (n.oid = c.relnamespace) LEFT JOIN pg_description d ON (c.oid = d.objoid) LEFT JOIN pg_attribute a ON (c.oid = a.attrelid AND a.attnum > 0 and a.attnum = d.objsubid)
|
||||
WHERE d.description IS NOT NULL AND n.nspname = in_schema AND c.relname = in_table ORDER BY 2 desc, ddl
|
||||
LOOP
|
||||
--RAISE NOTICE 'comments:%', v_rec.ddl;
|
||||
v_table_ddl = v_table_ddl || v_rec.ddl || E'\\n';
|
||||
END LOOP;
|
||||
END IF;
|
||||
IF bVerbose THEN RAISE NOTICE '(8)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
IF trigtype = 'INCLUDE_TRIGGERS' THEN
|
||||
-- Issue#14: handle multiple triggers for a table
|
||||
FOR v_trigrec IN
|
||||
select pg_get_triggerdef(t.oid, True) || ';' as triggerdef FROM pg_trigger t, pg_class c, pg_namespace n
|
||||
WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r' and t.tgrelid = c.oid and NOT t.tgisinternal
|
||||
LOOP
|
||||
v_table_ddl := v_table_ddl || v_trigrec.triggerdef;
|
||||
v_table_ddl := v_table_ddl || E'\\n';
|
||||
IF bVerbose THEN RAISE NOTICE 'triggerdef = %', v_trigrec.triggerdef; END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
IF bVerbose THEN RAISE NOTICE '(9)tabledef so far: %', v_table_ddl; END IF;
|
||||
-- add empty line
|
||||
v_table_ddl := v_table_ddl || E'\\n';
|
||||
IF bVerbose THEN RAISE NOTICE '(10)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- reset search_path back to what it was
|
||||
IF search_path_old = '' THEN
|
||||
SELECT set_config('search_path', '', false) into v_temp;
|
||||
ELSE
|
||||
EXECUTE 'SET search_path = ' || search_path_old;
|
||||
END IF;
|
||||
|
||||
RETURN v_table_ddl;
|
||||
|
||||
EXCEPTION
|
||||
WHEN others THEN
|
||||
BEGIN
|
||||
GET STACKED DIAGNOSTICS v_diag1 = MESSAGE_TEXT, v_diag2 = PG_EXCEPTION_DETAIL, v_diag3 = PG_EXCEPTION_HINT, v_diag4 = RETURNED_SQLSTATE, v_diag5 = PG_CONTEXT, v_diag6 = PG_EXCEPTION_CONTEXT;
|
||||
-- v_ret := 'line=' || v_diag6 || '. '|| v_diag4 || '. ' || v_diag1 || ' .' || v_diag2 || ' .' || v_diag3;
|
||||
v_ret := 'line=' || v_diag6 || '. '|| v_diag4 || '. ' || v_diag1;
|
||||
RAISE EXCEPTION '%', v_ret;
|
||||
-- put additional coding here if necessarY
|
||||
RETURN '';
|
||||
END;
|
||||
|
||||
END;
|
||||
$$;`,
|
||||
{ compress: true, removeAll: true }
|
||||
)
|
||||
@@ -1,80 +1,9 @@
|
||||
import { getEntityDefinitionsSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { CREATE_PG_GET_TABLEDEF_SQL } from './database-table-definition'
|
||||
import { databaseKeys } from './keys'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
export const getEntityDefinitionsSql = ({
|
||||
schemas,
|
||||
limit = 100,
|
||||
}: {
|
||||
schemas: string[]
|
||||
limit?: number
|
||||
}) => {
|
||||
const sql = /* SQL */ `
|
||||
${CREATE_PG_GET_TABLEDEF_SQL}
|
||||
|
||||
with records as (
|
||||
select
|
||||
c.oid::int8 as "id",
|
||||
case c.relkind
|
||||
when 'r' then pg_temp.pg_get_tabledef(
|
||||
concat(nc.nspname),
|
||||
concat(c.relname),
|
||||
false,
|
||||
'FKEYS_INTERNAL',
|
||||
'NO_TRIGGERS'
|
||||
)
|
||||
when 'v' then concat(
|
||||
'create view ', concat(nc.nspname, '.', c.relname), ' as',
|
||||
pg_get_viewdef(concat(nc.nspname, '.', c.relname), true)
|
||||
)
|
||||
when 'm' then concat(
|
||||
'create materialized view ', concat(nc.nspname, '.', c.relname), ' as',
|
||||
pg_get_viewdef(concat(nc.nspname, '.', c.relname), true)
|
||||
)
|
||||
when 'f' then concat('create foreign table ', nc.nspname, '.', c.relname, ' ( ... )')
|
||||
when 'p' then pg_temp.pg_get_tabledef(
|
||||
concat(nc.nspname),
|
||||
concat(c.relname),
|
||||
false,
|
||||
'FKEYS_INTERNAL',
|
||||
'NO_TRIGGERS'
|
||||
)
|
||||
end as "sql"
|
||||
from
|
||||
pg_namespace nc
|
||||
join pg_class c on nc.oid = c.relnamespace
|
||||
where
|
||||
c.relkind in ('r', 'v', 'm', 'f', 'p')
|
||||
and not pg_is_other_temp_schema(nc.oid)
|
||||
and (
|
||||
pg_has_role(c.relowner, 'USAGE')
|
||||
or has_table_privilege(
|
||||
c.oid,
|
||||
'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
|
||||
)
|
||||
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
and nc.nspname IN (${schemas.map((schema) => `'${schema}'`).join(', ')})
|
||||
order by c.relname asc
|
||||
limit ${limit}
|
||||
offset 0
|
||||
)
|
||||
select
|
||||
jsonb_build_object(
|
||||
'definitions', coalesce(jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', r.id,
|
||||
'sql', r.sql
|
||||
)
|
||||
), '[]'::jsonb)
|
||||
) "data"
|
||||
from records r;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
export type EntityDefinitionsVariables = {
|
||||
limit?: number
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getForeignKeyConstraintsSql } from '@supabase/pg-meta'
|
||||
import { QueryClient, useQuery } from '@tanstack/react-query'
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
@@ -40,61 +41,6 @@ export type ForeignKeyConstraint = {
|
||||
target_columns: string[]
|
||||
}
|
||||
|
||||
export const getForeignKeyConstraintsSql = ({ schema }: GetForeignKeyConstraintsVariables) => {
|
||||
if (!schema) throw new Error('schema is required')
|
||||
|
||||
const sql = /* SQL */ `
|
||||
SELECT
|
||||
con.oid as id,
|
||||
con.conname as constraint_name,
|
||||
con.confdeltype as deletion_action,
|
||||
con.confupdtype as update_action,
|
||||
rel.oid as source_id,
|
||||
nsp.nspname as source_schema,
|
||||
rel.relname as source_table,
|
||||
(
|
||||
SELECT
|
||||
array_agg(
|
||||
att.attname
|
||||
ORDER BY
|
||||
un.ord
|
||||
)
|
||||
FROM
|
||||
unnest(con.conkey) WITH ORDINALITY un (attnum, ord)
|
||||
INNER JOIN pg_attribute att ON att.attnum = un.attnum
|
||||
WHERE
|
||||
att.attrelid = rel.oid
|
||||
) source_columns,
|
||||
frel.oid as target_id,
|
||||
fnsp.nspname as target_schema,
|
||||
frel.relname as target_table,
|
||||
(
|
||||
SELECT
|
||||
array_agg(
|
||||
att.attname
|
||||
ORDER BY
|
||||
un.ord
|
||||
)
|
||||
FROM
|
||||
unnest(con.confkey) WITH ORDINALITY un (attnum, ord)
|
||||
INNER JOIN pg_attribute att ON att.attnum = un.attnum
|
||||
WHERE
|
||||
att.attrelid = frel.oid
|
||||
) target_columns
|
||||
FROM
|
||||
pg_constraint con
|
||||
INNER JOIN pg_class rel ON rel.oid = con.conrelid
|
||||
INNER JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
|
||||
INNER JOIN pg_class frel ON frel.oid = con.confrelid
|
||||
INNER JOIN pg_namespace fnsp ON fnsp.oid = frel.relnamespace
|
||||
WHERE
|
||||
con.contype = 'f'
|
||||
AND nsp.nspname = '${schema}'
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type ForeignKeyConstraintsVariables = GetForeignKeyConstraintsVariables & {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
@@ -104,6 +50,8 @@ export async function getForeignKeyConstraints(
|
||||
{ projectRef, connectionString, schema }: ForeignKeyConstraintsVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!schema) throw new Error('Schema is required')
|
||||
|
||||
const sql = getForeignKeyConstraintsSql({ schema })
|
||||
|
||||
const { result } = await executeSql(
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { getKeywordsSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
export const getKeywordsSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
SELECT word FROM pg_get_keywords();
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
export type KeywordsVariables = {
|
||||
projectRef?: string
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { getMaxConnectionsSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
export const getMaxConnectionsSql = () => {
|
||||
const sql = /* SQL */ `show max_connections`
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type MaxConnectionsVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { getMigrationsSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
@@ -9,17 +11,6 @@ export type DatabaseMigration = {
|
||||
statements?: string[]
|
||||
}
|
||||
|
||||
export const getMigrationsSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
select
|
||||
*
|
||||
from supabase_migrations.schema_migrations sm
|
||||
order by sm.version desc
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type MigrationsVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { getCheckPrimaryKeysExistsSQL } from '@supabase/pg-meta/src/sql/studio/check-primary-keys-exists'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError, UseCustomQueryOptions } from 'types'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
type CheckPrimaryKeysExistsVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
tables: { name: string; schema: string }[]
|
||||
}
|
||||
|
||||
type CheckPrimaryKeysExistResponse = {
|
||||
id: string
|
||||
name: string
|
||||
schema: string
|
||||
has_primary_key: boolean
|
||||
}[]
|
||||
|
||||
export async function checkPrimaryKeysExists({
|
||||
projectRef,
|
||||
connectionString,
|
||||
tables,
|
||||
}: CheckPrimaryKeysExistsVariables) {
|
||||
if (!projectRef) throw new Error('Project ref is required')
|
||||
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: getCheckPrimaryKeysExistsSQL(tables),
|
||||
})
|
||||
|
||||
return {
|
||||
offendingTables: (result as CheckPrimaryKeysExistResponse).filter((x) => !x.has_primary_key),
|
||||
}
|
||||
}
|
||||
|
||||
export type CheckPrimaryKeysExistsData = Awaited<ReturnType<typeof checkPrimaryKeysExists>>
|
||||
export type CheckPrimaryKeysExistsError = ResponseError
|
||||
|
||||
export const useCheckPrimaryKeysExists = <TData = CheckPrimaryKeysExistsData>(
|
||||
{ projectRef, connectionString, tables }: CheckPrimaryKeysExistsVariables,
|
||||
{
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseCustomQueryOptions<CheckPrimaryKeysExistsData, CheckPrimaryKeysExistsError, TData> = {}
|
||||
) =>
|
||||
useQuery<CheckPrimaryKeysExistsData, CheckPrimaryKeysExistsError, TData>({
|
||||
queryKey: databaseKeys.checkPrimaryKeysExists(projectRef, tables),
|
||||
queryFn: () => checkPrimaryKeysExists({ projectRef, connectionString, tables }),
|
||||
retry: false,
|
||||
enabled: enabled && typeof projectRef !== 'undefined' && tables.length > 0,
|
||||
...options,
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
import { getTableColumnsSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
@@ -11,81 +13,6 @@ export type TableColumn = {
|
||||
columns: any[]
|
||||
}
|
||||
|
||||
export const getTableColumnsSql = ({ table, schema }: { table?: string; schema?: string }) => {
|
||||
const conditions = []
|
||||
if (table) {
|
||||
conditions.push(`tablename = '${table}'`)
|
||||
}
|
||||
if (schema) {
|
||||
conditions.push(`schemaname = '${schema}'`)
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
||||
|
||||
const sql = /* SQL */ `
|
||||
|
||||
SELECT
|
||||
tbl.schemaname,
|
||||
tbl.tablename,
|
||||
tbl.quoted_name,
|
||||
tbl.is_table,
|
||||
json_agg(a) as columns
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
n.nspname as schemaname,
|
||||
c.relname as tablename,
|
||||
(quote_ident(n.nspname) || '.' || quote_ident(c.relname)) as quoted_name,
|
||||
true as is_table
|
||||
FROM
|
||||
pg_catalog.pg_class c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE
|
||||
c.relkind = 'r'
|
||||
AND n.nspname not in ('information_schema', 'pg_catalog', 'pg_toast')
|
||||
AND n.nspname not like 'pg_temp_%'
|
||||
AND n.nspname not like 'pg_toast_temp_%'
|
||||
AND has_schema_privilege(n.oid, 'USAGE') = true
|
||||
AND has_table_privilege(quote_ident(n.nspname) || '.' || quote_ident(c.relname), 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER') = true
|
||||
union all
|
||||
SELECT
|
||||
n.nspname as schemaname,
|
||||
c.relname as tablename,
|
||||
(quote_ident(n.nspname) || '.' || quote_ident(c.relname)) as quoted_name,
|
||||
false as is_table
|
||||
FROM
|
||||
pg_catalog.pg_class c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE
|
||||
c.relkind in ('v', 'm')
|
||||
AND n.nspname not in ('information_schema', 'pg_catalog', 'pg_toast')
|
||||
AND n.nspname not like 'pg_temp_%'
|
||||
AND n.nspname not like 'pg_toast_temp_%'
|
||||
AND has_schema_privilege(n.oid, 'USAGE') = true
|
||||
AND has_table_privilege(quote_ident(n.nspname) || '.' || quote_ident(c.relname), 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER') = true
|
||||
) as tbl
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
attrelid,
|
||||
attname,
|
||||
format_type(atttypid, atttypmod) as data_type,
|
||||
attnum,
|
||||
attisdropped
|
||||
FROM
|
||||
pg_attribute
|
||||
) as a ON (
|
||||
a.attrelid = tbl.quoted_name::regclass
|
||||
AND a.attnum > 0
|
||||
AND NOT a.attisdropped
|
||||
AND has_column_privilege(tbl.quoted_name, a.attname, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
${whereClause}
|
||||
GROUP BY schemaname, tablename, quoted_name, is_table;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type TableColumnsVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
|
||||
@@ -1,39 +1,14 @@
|
||||
import { getTableDefinitionSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { CREATE_PG_GET_TABLEDEF_SQL } from './database-table-definition'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
type GetTableDefinitionArgs = {
|
||||
id?: number
|
||||
}
|
||||
|
||||
// [Joshen] Eventually move this into entity-definition-query
|
||||
const getTableDefinitionSql = ({ id }: GetTableDefinitionArgs) => {
|
||||
const sql = /* SQL */ `
|
||||
${CREATE_PG_GET_TABLEDEF_SQL}
|
||||
|
||||
with table_info as (
|
||||
select
|
||||
n.nspname::text as schema,
|
||||
c.relname::text as name
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.oid = ${id}
|
||||
)
|
||||
select pg_temp.pg_get_tabledef (
|
||||
t.schema,
|
||||
t.name,
|
||||
false,
|
||||
'FKEYS_INTERNAL',
|
||||
'INCLUDE_TRIGGERS'
|
||||
) as definition
|
||||
from table_info t;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type TableDefinitionVariables = GetTableDefinitionArgs & {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
@@ -43,9 +18,7 @@ export async function getTableDefinition(
|
||||
{ projectRef, connectionString, id }: TableDefinitionVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!id) {
|
||||
throw new Error('id is required')
|
||||
}
|
||||
if (!id) throw new Error('id is required')
|
||||
|
||||
const sql = getTableDefinitionSql({ id })
|
||||
const { result } = await executeSql(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getTableIndexAdvisorSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { filterProtectedSchemaIndexStatements } from 'components/interfaces/QueryPerformance/IndexAdvisor/index-advisor.utils'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError, UseCustomQueryOptions } from 'types'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
import { filterProtectedSchemaIndexStatements } from 'components/interfaces/QueryPerformance/IndexAdvisor/index-advisor.utils'
|
||||
import { databaseKeys } from './keys'
|
||||
|
||||
export type TableIndexAdvisorVariables = {
|
||||
projectRef?: string
|
||||
@@ -31,60 +31,6 @@ export type TableIndexAdvisorResponse = {
|
||||
columnsWithSuggestions: string[]
|
||||
}
|
||||
|
||||
//Generates SQL to find top 5 SELECT queries involving a table and run them through index_advisor
|
||||
export function getTableIndexAdvisorSql(schema: string, table: string): string {
|
||||
const escapedSchema = schema.replace(/'/g, "''")
|
||||
const escapedTable = table.replace(/'/g, "''")
|
||||
|
||||
// Escape regex metacharacters so schema/table names are matched literally
|
||||
const regexSchema = escapedSchema.toLowerCase().replace(/[.+*?^${}()|[\]\\]/g, '\\$&')
|
||||
const regexTable = escapedTable.toLowerCase().replace(/[.+*?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
return /* SQL */ `
|
||||
-- Get top 5 SELECT queries involving this table and run through index_advisor
|
||||
set search_path to public, extensions;
|
||||
|
||||
with top_queries as (
|
||||
select
|
||||
statements.query,
|
||||
statements.calls,
|
||||
statements.total_exec_time + statements.total_plan_time as total_time,
|
||||
statements.mean_exec_time + statements.mean_plan_time as mean_time
|
||||
from pg_stat_statements as statements
|
||||
inner join pg_authid as auth on statements.userid = auth.oid
|
||||
where
|
||||
-- Filter for SELECT queries only (index_advisor only works with SELECT)
|
||||
(lower(statements.query) like 'select%' or lower(statements.query) like 'with pgrst%')
|
||||
-- Filter for queries involving our table. Use regex word boundaries so that e.g.
|
||||
-- looking for table "orders" does not match queries on "orders_items".
|
||||
and (
|
||||
lower(statements.query) ~ '(^|[^a-z0-9_$])${regexSchema}[.]${regexTable}($|[^a-z0-9_$])'
|
||||
or lower(statements.query) ~ '(^|[^a-z0-9_$])from[[:space:]]+${regexTable}($|[^a-z0-9_$])'
|
||||
or lower(statements.query) ~ '(^|[^a-z0-9_$])join[[:space:]]+${regexTable}($|[^a-z0-9_$])'
|
||||
)
|
||||
-- Exclude system queries
|
||||
and statements.query not like '%pg_catalog%'
|
||||
and statements.query not like '%information_schema%'
|
||||
order by statements.calls desc
|
||||
limit 5
|
||||
)
|
||||
select
|
||||
tq.query,
|
||||
tq.calls,
|
||||
tq.total_time,
|
||||
tq.mean_time,
|
||||
coalesce(ia.index_statements, '{}') as index_statements,
|
||||
coalesce((ia.startup_cost_before)::numeric, 0) as startup_cost_before,
|
||||
coalesce((ia.startup_cost_after)::numeric, 0) as startup_cost_after,
|
||||
coalesce((ia.total_cost_before)::numeric, 0) as total_cost_before,
|
||||
coalesce((ia.total_cost_after)::numeric, 0) as total_cost_after
|
||||
from top_queries tq
|
||||
left join lateral (
|
||||
select * from index_advisor(tq.query)
|
||||
) ia on true;
|
||||
`.trim()
|
||||
}
|
||||
|
||||
// Strips ordering modifiers and outer quotes from a raw column token from an index statement.
|
||||
// e.g. '"created_at" DESC NULLS LAST' -> 'created_at'
|
||||
export function cleanIndexColumnName(raw: string): string {
|
||||
|
||||
@@ -1,35 +1,14 @@
|
||||
import { getViewDefinitionSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { databaseKeys } from './keys'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
type GetViewDefinitionArgs = {
|
||||
id?: number
|
||||
}
|
||||
|
||||
// [Joshen] Eventually move this into entity-definition-query
|
||||
export const getViewDefinitionSql = ({ id }: GetViewDefinitionArgs) => {
|
||||
if (!id) {
|
||||
throw new Error('id is required')
|
||||
}
|
||||
|
||||
const sql = /* SQL */ `
|
||||
with table_info as (
|
||||
select
|
||||
n.nspname::text as schema,
|
||||
c.relname::text as name,
|
||||
to_regclass(concat('"', n.nspname, '"."', c.relname, '"')) as regclass
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.oid = ${id}
|
||||
)
|
||||
select pg_get_viewdef(t.regclass, true) as definition
|
||||
from table_info t
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type ViewDefinitionVariables = GetViewDefinitionArgs & {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
@@ -39,6 +18,8 @@ export async function getViewDefinition(
|
||||
{ projectRef, connectionString, id }: ViewDefinitionVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!id) throw new Error('View ID is required')
|
||||
|
||||
const sql = getViewDefinitionSql({ id })
|
||||
const { result } = await executeSql(
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { getEntityTypesSQL } from '@supabase/pg-meta'
|
||||
import { InfiniteData, QueryClient, useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { executeSql, ExecuteSqlVariables } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError, UseCustomInfiniteQueryOptions } from 'types'
|
||||
|
||||
import { ENTITY_TYPE } from './entity-type-constants'
|
||||
import { entityTypeKeys } from './keys'
|
||||
|
||||
@@ -43,63 +45,7 @@ export async function getEntityTypes(
|
||||
}: EntityTypesVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
const innerOrderBy = sort === 'alphabetical' ? `c.relname asc` : `"type_sort" asc, c.relname asc`
|
||||
const outerOrderBy = sort === 'alphabetical' ? `r.name asc` : `r.type_sort asc, r.name asc`
|
||||
|
||||
const sql = /* SQL */ `
|
||||
with records as (
|
||||
select
|
||||
c.oid::int8 as "id",
|
||||
nc.nspname as "schema",
|
||||
c.relname as "name",
|
||||
c.relkind as "type",
|
||||
case c.relkind
|
||||
when 'r' then 1
|
||||
when 'v' then 2
|
||||
when 'm' then 3
|
||||
when 'f' then 4
|
||||
when 'p' then 5
|
||||
end as "type_sort",
|
||||
obj_description(c.oid) as "comment",
|
||||
count(*) over() as "count",
|
||||
c.relrowsecurity as "rls_enabled"
|
||||
from
|
||||
pg_namespace nc
|
||||
join pg_class c on nc.oid = c.relnamespace
|
||||
where
|
||||
c.relkind in (${filterTypes.map((x) => `'${x}'`).join(', ')})
|
||||
and not pg_is_other_temp_schema(nc.oid)
|
||||
and (
|
||||
pg_has_role(c.relowner, 'USAGE')
|
||||
or has_table_privilege(
|
||||
c.oid,
|
||||
'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
|
||||
)
|
||||
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
and nc.nspname in (${schemas.map((x) => `'${x}'`)})
|
||||
${search ? `and c.relname ilike '%${search}%'` : ''}
|
||||
order by ${innerOrderBy}
|
||||
limit ${limit}
|
||||
offset ${page * limit}
|
||||
)
|
||||
select
|
||||
jsonb_build_object(
|
||||
'entities', coalesce(jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', r.id,
|
||||
'schema', r.schema,
|
||||
'name', r.name,
|
||||
'type', r.type,
|
||||
'comment', r.comment,
|
||||
'rls_enabled', r.rls_enabled
|
||||
)
|
||||
order by ${outerOrderBy}
|
||||
), '[]'::jsonb),
|
||||
'count', coalesce(min(r.count), 0)
|
||||
) "data"
|
||||
from records r;
|
||||
`
|
||||
const sql = getEntityTypesSQL({ schemas, sort, filterTypes, search, limit, page })
|
||||
|
||||
const { result } = await executeSql(
|
||||
{
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
import { replicationLagSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { replicaKeys } from './keys'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
export const replicationLagSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
select
|
||||
case
|
||||
when (select count(*) from pg_stat_wal_receiver) = 1 and pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn()
|
||||
then 0
|
||||
else coalesce(extract(epoch from now() - pg_last_xact_replay_timestamp()),0)
|
||||
end as physical_replica_lag_second
|
||||
`
|
||||
|
||||
return sql
|
||||
}
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { replicaKeys } from './keys'
|
||||
|
||||
export type ReplicationLagVariables = {
|
||||
id: string
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { getAbortQuerySQL } from '@supabase/pg-meta'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { toast } from 'sonner'
|
||||
import type { ResponseError, UseCustomMutationOptions } from 'types'
|
||||
|
||||
import { sqlKeys } from './keys'
|
||||
|
||||
export type QueryAbortVariables = {
|
||||
@@ -12,7 +13,7 @@ export type QueryAbortVariables = {
|
||||
}
|
||||
|
||||
export async function abortQuery({ pid, projectRef, connectionString }: QueryAbortVariables) {
|
||||
const sql = /* SQL */ `select pg_terminate_backend(${pid})`
|
||||
const sql = getAbortQuerySQL({ pid })
|
||||
const { result } = await executeSql({ projectRef, connectionString, sql })
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { getOngoingQueriesSql } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { sqlKeys } from './keys'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
type OngoingQuery = {
|
||||
pid: number
|
||||
@@ -9,14 +11,6 @@ type OngoingQuery = {
|
||||
query_start: string
|
||||
}
|
||||
|
||||
export const getOngoingQueriesSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
select pid, query, query_start from pg_stat_activity where state = 'active' and datname = 'postgres';
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type OngoingQueriesVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
@@ -26,7 +20,7 @@ export async function getOngoingQueries(
|
||||
{ projectRef, connectionString }: OngoingQueriesVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
const sql = getOngoingQueriesSql().trim()
|
||||
const sql = getOngoingQueriesSql()
|
||||
|
||||
const { result } = await executeSql(
|
||||
{ projectRef, connectionString, sql, queryKey: ['ongoing-queries'] },
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { getTablesWithAnonAuthenticatedAccessSQL } from '@supabase/pg-meta/src/sql/studio/check-tables-anon-authenticated-access'
|
||||
import { getTablesWithAnonAuthenticatedAccessSQL } from '@supabase/pg-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
||||
import { tableKeys } from './keys'
|
||||
import { UseCustomQueryOptions } from 'types'
|
||||
|
||||
type TablesRolesAccessArgs = {
|
||||
schema: string
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { QUEUES_SCHEMA } from '@supabase/pg-meta'
|
||||
import {
|
||||
SUPABASE_TARGET_SCHEMA_OPTION,
|
||||
WRAPPERS,
|
||||
@@ -11,7 +12,6 @@ import { uniq, uniqBy } from 'lodash'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useSelectedProjectQuery } from './misc/useSelectedProject'
|
||||
import { QUEUES_SCHEMA } from '@/data/database-queues/database-queues-toggle-postgrest-mutation'
|
||||
|
||||
/**
|
||||
* A list of system schemas that users should not interact with
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { getEntityDefinitionsSql } from '@supabase/pg-meta'
|
||||
import { tool } from 'ai'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import { z } from 'zod'
|
||||
|
||||
// import { processSql, renderSupabaseJs } from '@supabase/sql-to-rest'
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import { getDatabaseFunctions } from 'data/database-functions/database-functions-query'
|
||||
import { getDatabasePolicies } from 'data/database-policies/database-policies-query'
|
||||
import { getEntityDefinitionsSql } from 'data/database/entity-definitions-query'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { executeQuery } from 'lib/api/self-hosted/query'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const getFallbackTools = ({
|
||||
projectRef,
|
||||
|
||||
@@ -18,56 +18,18 @@ import version from './pg-meta-version'
|
||||
import views from './pg-meta-views'
|
||||
import * as query from './query/index'
|
||||
|
||||
// Studio SQL queries
|
||||
export {
|
||||
getUserSQL,
|
||||
getIndexStatusesSQL,
|
||||
getUsersCountSQL,
|
||||
getPaginatedUsersSQL,
|
||||
getIndexWorkerStatusSQL,
|
||||
USER_SEARCH_INDEXES,
|
||||
type OptimizedSearchColumns,
|
||||
type UsersCursor,
|
||||
} from './sql/studio/auth'
|
||||
export {
|
||||
getLargestSizeLimitBucketsSqlUnoptimized,
|
||||
LARGEST_SIZE_LIMIT_BUCKETS_COUNT,
|
||||
} from './sql/studio/storage'
|
||||
export {
|
||||
getExposedTablesSql,
|
||||
getExposedTableCountsSql,
|
||||
getExposedFunctionsSql,
|
||||
getExposedFunctionCountsSql,
|
||||
buildTablePrivilegesSql,
|
||||
buildFunctionPrivilegesSql,
|
||||
buildDefaultPrivilegesSql,
|
||||
getDefaultPrivilegesStateSql,
|
||||
} from './sql/studio/privileges'
|
||||
export {
|
||||
getIndexesSQL,
|
||||
getDatabaseExtensionDefaultSchemaSQL,
|
||||
getCronJobsMinimalSql,
|
||||
getCronJobsSql,
|
||||
getJobRunDetailsPageCountSql,
|
||||
getDeleteOldCronJobRunDetailsByCtidSql,
|
||||
getScheduleDeleteCronJobRunDetailsSql,
|
||||
getTableRowsCountSql,
|
||||
} from './sql/studio/database'
|
||||
export {
|
||||
type ForeignKey,
|
||||
FOREIGN_KEY_CASCADE_ACTION,
|
||||
getAddPrimaryKeySQL,
|
||||
getDropConstraintSQL,
|
||||
getAddForeignKeySQL,
|
||||
getRemoveForeignKeySQL,
|
||||
getUpdateIdentitySequenceSQL,
|
||||
getDuplicateIdentitySequenceSQL,
|
||||
getDuplicateTableSQL,
|
||||
getDuplicateRowsSQL,
|
||||
getEnableRLSSQL,
|
||||
} from './sql/studio/table-editor'
|
||||
|
||||
export { getLiveTupleEstimate } from './sql/studio/get-live-tuple-estimate'
|
||||
/**
|
||||
* Studio specific SQL queries
|
||||
* [Joshen] If it gets cumbersome, we can also consider path exports for studio queries
|
||||
* So consumption can look something like:
|
||||
* import { ... } from '@supabase/pg-meta/table-editor'
|
||||
*/
|
||||
export * from './sql/studio/advisor'
|
||||
export * from './sql/studio/auth'
|
||||
export * from './sql/studio/storage'
|
||||
export * from './sql/studio/database'
|
||||
export * from './sql/studio/table-editor'
|
||||
export * from './sql/studio/sql-editor'
|
||||
|
||||
export default {
|
||||
roles,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Generates SQL to find top 5 SELECT queries involving a table and run them through index_advisor
|
||||
*/
|
||||
export function getTableIndexAdvisorSql(schema: string, table: string): string {
|
||||
const escapedSchema = schema.replace(/'/g, "''")
|
||||
const escapedTable = table.replace(/'/g, "''")
|
||||
|
||||
// Escape regex metacharacters so schema/table names are matched literally
|
||||
const regexSchema = escapedSchema.toLowerCase().replace(/[.+*?^${}()|[\]\\]/g, '\\$&')
|
||||
const regexTable = escapedTable.toLowerCase().replace(/[.+*?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
return /* SQL */ `
|
||||
-- Get top 5 SELECT queries involving this table and run through index_advisor
|
||||
set search_path to public, extensions;
|
||||
|
||||
with top_queries as (
|
||||
select
|
||||
statements.query,
|
||||
statements.calls,
|
||||
statements.total_exec_time + statements.total_plan_time as total_time,
|
||||
statements.mean_exec_time + statements.mean_plan_time as mean_time
|
||||
from pg_stat_statements as statements
|
||||
inner join pg_authid as auth on statements.userid = auth.oid
|
||||
where
|
||||
-- Filter for SELECT queries only (index_advisor only works with SELECT)
|
||||
(lower(statements.query) like 'select%' or lower(statements.query) like 'with pgrst%')
|
||||
-- Filter for queries involving our table. Use regex word boundaries so that e.g.
|
||||
-- looking for table "orders" does not match queries on "orders_items".
|
||||
and (
|
||||
lower(statements.query) ~ '(^|[^a-z0-9_$])${regexSchema}[.]${regexTable}($|[^a-z0-9_$])'
|
||||
or lower(statements.query) ~ '(^|[^a-z0-9_$])from[[:space:]]+${regexTable}($|[^a-z0-9_$])'
|
||||
or lower(statements.query) ~ '(^|[^a-z0-9_$])join[[:space:]]+${regexTable}($|[^a-z0-9_$])'
|
||||
)
|
||||
-- Exclude system queries
|
||||
and statements.query not like '%pg_catalog%'
|
||||
and statements.query not like '%information_schema%'
|
||||
order by statements.calls desc
|
||||
limit 5
|
||||
)
|
||||
select
|
||||
tq.query,
|
||||
tq.calls,
|
||||
tq.total_time,
|
||||
tq.mean_time,
|
||||
coalesce(ia.index_statements, '{}') as index_statements,
|
||||
coalesce((ia.startup_cost_before)::numeric, 0) as startup_cost_before,
|
||||
coalesce((ia.startup_cost_after)::numeric, 0) as startup_cost_after,
|
||||
coalesce((ia.total_cost_before)::numeric, 0) as total_cost_before,
|
||||
coalesce((ia.total_cost_after)::numeric, 0) as total_cost_after
|
||||
from top_queries tq
|
||||
left join lateral (
|
||||
select * from index_advisor(tq.query)
|
||||
) ia on true;
|
||||
`.trim()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './index-advisor'
|
||||
@@ -1,5 +1,5 @@
|
||||
import { literal } from '../../../pg-format'
|
||||
import { COUNT_ESTIMATE_SQL, THRESHOLD_COUNT } from '../get-count-estimate'
|
||||
import { COUNT_ESTIMATE_SQL, THRESHOLD_COUNT } from '../database/get-count-estimate'
|
||||
import { prefixToUUID, stringRange } from './get-users-common'
|
||||
import type { OptimizedSearchColumns } from './get-users-types'
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
export const getCheckPrimaryKeysExistsSQL = (tables: { name: string; schema: string }[]) => {
|
||||
const formattedTables = tables.map((table) => `'${table.schema}.${table.name}'`).join(',')
|
||||
|
||||
return /* SQL */ `
|
||||
WITH targets(rel) AS (
|
||||
SELECT unnest(ARRAY[${formattedTables}]::regclass[])
|
||||
)
|
||||
SELECT
|
||||
c.oid AS id,
|
||||
n.nspname AS schema,
|
||||
c.relname AS name,
|
||||
(con.conrelid IS NOT NULL) AS has_primary_key
|
||||
FROM targets t
|
||||
JOIN pg_class c ON c.oid = t.rel
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
LEFT JOIN pg_constraint con
|
||||
ON con.conrelid = c.oid AND con.contype = 'p'
|
||||
ORDER BY n.nspname, c.relname;
|
||||
`
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,74 @@
|
||||
export const getTableColumnsSql = ({ table, schema }: { table?: string; schema?: string }) => {
|
||||
const conditions = []
|
||||
if (table) {
|
||||
conditions.push(`tablename = '${table}'`)
|
||||
}
|
||||
if (schema) {
|
||||
conditions.push(`schemaname = '${schema}'`)
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
||||
|
||||
const sql = /* SQL */ `
|
||||
|
||||
SELECT
|
||||
tbl.schemaname,
|
||||
tbl.tablename,
|
||||
tbl.quoted_name,
|
||||
tbl.is_table,
|
||||
json_agg(a) as columns
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
n.nspname as schemaname,
|
||||
c.relname as tablename,
|
||||
(quote_ident(n.nspname) || '.' || quote_ident(c.relname)) as quoted_name,
|
||||
true as is_table
|
||||
FROM
|
||||
pg_catalog.pg_class c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE
|
||||
c.relkind = 'r'
|
||||
AND n.nspname not in ('information_schema', 'pg_catalog', 'pg_toast')
|
||||
AND n.nspname not like 'pg_temp_%'
|
||||
AND n.nspname not like 'pg_toast_temp_%'
|
||||
AND has_schema_privilege(n.oid, 'USAGE') = true
|
||||
AND has_table_privilege(quote_ident(n.nspname) || '.' || quote_ident(c.relname), 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER') = true
|
||||
union all
|
||||
SELECT
|
||||
n.nspname as schemaname,
|
||||
c.relname as tablename,
|
||||
(quote_ident(n.nspname) || '.' || quote_ident(c.relname)) as quoted_name,
|
||||
false as is_table
|
||||
FROM
|
||||
pg_catalog.pg_class c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE
|
||||
c.relkind in ('v', 'm')
|
||||
AND n.nspname not in ('information_schema', 'pg_catalog', 'pg_toast')
|
||||
AND n.nspname not like 'pg_temp_%'
|
||||
AND n.nspname not like 'pg_toast_temp_%'
|
||||
AND has_schema_privilege(n.oid, 'USAGE') = true
|
||||
AND has_table_privilege(quote_ident(n.nspname) || '.' || quote_ident(c.relname), 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER') = true
|
||||
) as tbl
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
attrelid,
|
||||
attname,
|
||||
format_type(atttypid, atttypmod) as data_type,
|
||||
attnum,
|
||||
attisdropped
|
||||
FROM
|
||||
pg_attribute
|
||||
) as a ON (
|
||||
a.attrelid = tbl.quoted_name::regclass
|
||||
AND a.attnum > 0
|
||||
AND NOT a.attisdropped
|
||||
AND has_column_privilege(tbl.quoted_name, a.attname, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
${whereClause}
|
||||
GROUP BY schemaname, tablename, quoted_name, is_table;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
File renamed without changes.
@@ -1,4 +1,14 @@
|
||||
export * from './check-tables-anon-authenticated-access'
|
||||
export * from './columns'
|
||||
export * from './cron-jobs'
|
||||
export * from './extensions'
|
||||
export * from './get-count-estimate'
|
||||
export * from './indexes'
|
||||
export * from './migrations'
|
||||
export * from './misc'
|
||||
export * from './privileges'
|
||||
export * from './queues'
|
||||
export * from './rows'
|
||||
export * from './table-definition'
|
||||
export * from './triggers'
|
||||
export * from './views'
|
||||
@@ -0,0 +1,10 @@
|
||||
export const getMigrationsSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
select
|
||||
*
|
||||
from supabase_migrations.schema_migrations sm
|
||||
order by sm.version desc
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { literal } from '../../../pg-format'
|
||||
|
||||
export const getDatabaseSizeSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
select sum(pg_database_size(pg_database.datname))::bigint as db_size from pg_database;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export const getLiveTupleEstimate = (table: string, schema: string = 'public') => {
|
||||
const sql = /* SQL */ `
|
||||
SELECT n_live_tup AS live_tuple_estimate
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname = ${literal(schema)}
|
||||
AND relname = ${literal(table)};
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export const getMaxConnectionsSql = () => {
|
||||
const sql = /* SQL */ `show max_connections`
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export const replicationLagSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
select
|
||||
case
|
||||
when (select count(*) from pg_stat_wal_receiver) = 1 and pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn()
|
||||
then 0
|
||||
else coalesce(extract(epoch from now() - pg_last_xact_replay_timestamp()),0)
|
||||
end as physical_replica_lag_second
|
||||
`
|
||||
|
||||
return sql
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,215 @@
|
||||
export const QUEUES_SCHEMA = 'pgmq_public'
|
||||
|
||||
export const HIDE_QUEUES_FROM_POSTGREST_SQL = /* SQL */ `
|
||||
drop function if exists
|
||||
${QUEUES_SCHEMA}.pop(queue_name text),
|
||||
${QUEUES_SCHEMA}.send(queue_name text, message jsonb, sleep_seconds integer),
|
||||
${QUEUES_SCHEMA}.send_batch(queue_name text, message jsonb[], sleep_seconds integer),
|
||||
${QUEUES_SCHEMA}.archive(queue_name text, message_id bigint),
|
||||
${QUEUES_SCHEMA}.delete(queue_name text, message_id bigint),
|
||||
${QUEUES_SCHEMA}.read(queue_name text, sleep integer, n integer)
|
||||
;
|
||||
|
||||
-- Revoke execute permissions on inner pgmq functions to roles (inverse of enabling)
|
||||
do $$
|
||||
begin
|
||||
if exists (select 1 from pg_namespace where nspname = 'pgmq') then
|
||||
-- Revoke privileges on the schema itself
|
||||
revoke all on schema pgmq from anon, authenticated, service_role;
|
||||
|
||||
-- Revoke default privileges for future objects
|
||||
alter default privileges in schema pgmq revoke all on tables from anon, authenticated, service_role;
|
||||
alter default privileges in schema pgmq revoke all on sequences from anon, authenticated, service_role;
|
||||
alter default privileges in schema pgmq revoke all on functions from anon, authenticated, service_role;
|
||||
end if;
|
||||
end $$;
|
||||
|
||||
drop schema if exists ${QUEUES_SCHEMA};
|
||||
`
|
||||
|
||||
export const getExposeQueuesSQL = ({ isNewerPgmqversion }: { isNewerPgmqversion: boolean }) => {
|
||||
const conditionalJsonb = isNewerPgmqversion ? `, conditional := '{}'::jsonb` : ''
|
||||
const jsonBArg = isNewerPgmqversion ? `, jsonb` : ''
|
||||
|
||||
return /* SQL */ `
|
||||
create schema if not exists ${QUEUES_SCHEMA};
|
||||
grant usage on schema ${QUEUES_SCHEMA} to postgres, anon, authenticated, service_role;
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.pop(
|
||||
queue_name text
|
||||
)
|
||||
returns setof pgmq.message_record
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select *
|
||||
from pgmq.pop(
|
||||
queue_name := queue_name
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.pop(queue_name text) is 'Retrieves and locks the next message from the specified queue.';
|
||||
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.send(
|
||||
queue_name text,
|
||||
message jsonb,
|
||||
sleep_seconds integer default 0 -- renamed from 'delay'
|
||||
)
|
||||
returns setof bigint
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select *
|
||||
from pgmq.send(
|
||||
queue_name := queue_name,
|
||||
msg := message,
|
||||
delay := sleep_seconds
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.send(queue_name text, message jsonb, sleep_seconds integer) is 'Sends a message to the specified queue, optionally delaying its availability by a number of seconds.';
|
||||
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.send_batch(
|
||||
queue_name text,
|
||||
messages jsonb[],
|
||||
sleep_seconds integer default 0 -- renamed from 'delay'
|
||||
)
|
||||
returns setof bigint
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select *
|
||||
from pgmq.send_batch(
|
||||
queue_name := queue_name,
|
||||
msgs := messages,
|
||||
delay := sleep_seconds
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.send_batch(queue_name text, messages jsonb[], sleep_seconds integer) is 'Sends a batch of messages to the specified queue, optionally delaying their availability by a number of seconds.';
|
||||
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.archive(
|
||||
queue_name text,
|
||||
message_id bigint
|
||||
)
|
||||
returns boolean
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return
|
||||
pgmq.archive(
|
||||
queue_name := queue_name,
|
||||
msg_id := message_id
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.archive(queue_name text, message_id bigint) is 'Archives a message by moving it from the queue to a permanent archive.';
|
||||
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.delete(
|
||||
queue_name text,
|
||||
message_id bigint
|
||||
)
|
||||
returns boolean
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return
|
||||
pgmq.delete(
|
||||
queue_name := queue_name,
|
||||
msg_id := message_id
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.delete(queue_name text, message_id bigint) is 'Permanently deletes a message from the specified queue.';
|
||||
|
||||
create or replace function ${QUEUES_SCHEMA}.read(
|
||||
queue_name text,
|
||||
sleep_seconds integer,
|
||||
n integer
|
||||
)
|
||||
returns setof pgmq.message_record
|
||||
language plpgsql
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
return query
|
||||
select *
|
||||
from pgmq.read(
|
||||
queue_name := queue_name,
|
||||
vt := sleep_seconds,
|
||||
qty := n ${conditionalJsonb}
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function ${QUEUES_SCHEMA}.read(queue_name text, sleep_seconds integer, n integer) is 'Reads up to "n" messages from the specified queue with an optional "sleep_seconds" (visibility timeout).';
|
||||
|
||||
-- Grant execute permissions on wrapper functions to roles
|
||||
grant execute on function ${QUEUES_SCHEMA}.pop(text) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.pop(text) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.send(text, jsonb, integer) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.send(text, jsonb, integer) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.send_batch(text, jsonb[], integer) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.send_batch(text, jsonb[], integer) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.archive(text, bigint) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.archive(text, bigint) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.delete(text, bigint) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.delete(text, bigint) to postgres, service_role, anon, authenticated;
|
||||
|
||||
grant execute on function ${QUEUES_SCHEMA}.read(text, integer, integer) to postgres, service_role, anon, authenticated;
|
||||
grant execute on function pgmq.read(text, integer, integer ${jsonBArg}) to postgres, service_role, anon, authenticated;
|
||||
|
||||
-- For the service role, we want full access
|
||||
-- Grant permissions on existing tables
|
||||
grant all privileges on all tables in schema pgmq to postgres, service_role;
|
||||
|
||||
-- Ensure service_role has permissions on future tables
|
||||
alter default privileges in schema pgmq grant all privileges on tables to postgres, service_role;
|
||||
|
||||
grant usage on schema pgmq to postgres, anon, authenticated, service_role;
|
||||
|
||||
|
||||
/*
|
||||
Grant access to sequences to API roles by default. Existing table permissions
|
||||
continue to enforce insert restrictions. This is necessary to accommodate the
|
||||
on-backup hook that rebuild queue table primary keys to avoid a pg_dump segfault.
|
||||
This can be removed once logical backups are completely retired.
|
||||
*/
|
||||
grant usage, select, update
|
||||
on all sequences in schema pgmq
|
||||
to anon, authenticated, service_role;
|
||||
|
||||
alter default privileges in schema pgmq
|
||||
grant usage, select, update
|
||||
on sequences
|
||||
to anon, authenticated, service_role;
|
||||
`.trim()
|
||||
}
|
||||
|
||||
// [Joshen] Check if all the relevant functions exist to indicate whether PGMQ has been exposed through PostgREST
|
||||
export const getQueuesExposePostgrestStatusSQL = () => {
|
||||
return /**SQL */ `
|
||||
SELECT exists (select schema_name FROM information_schema.schemata WHERE schema_name = '${QUEUES_SCHEMA}');
|
||||
`.trim()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Filter, Query } from '../../../query'
|
||||
import { COUNT_ESTIMATE_SQL, THRESHOLD_COUNT } from '../get-count-estimate'
|
||||
import { COUNT_ESTIMATE_SQL, THRESHOLD_COUNT } from './get-count-estimate'
|
||||
|
||||
/**
|
||||
* [Joshen] Initially check reltuples from pg_class for an estimate of row count on the table
|
||||
|
||||
@@ -0,0 +1,802 @@
|
||||
/**
|
||||
* Derived from https://github.com/MichaelDBA/pg_get_tabledef
|
||||
* NOTE: when updating, \n must be replaced with \\n in the SQL below
|
||||
* public.x is replaced with pg_temp.x
|
||||
|
||||
* REMOVE (as it breaks the encoding for some reason):
|
||||
* SELECT REPLACE(REPLACE(setting, '"$user"', '$user'), '$user', '"$user"') INTO search_path_old
|
||||
* FROM pg_settings
|
||||
* WHERE name = 'search_path';
|
||||
*/
|
||||
|
||||
export const CREATE_PG_GET_TABLEDEF_SQL = /* SQL */ `
|
||||
DROP TYPE IF EXISTS pg_temp.tabledefs CASCADE;
|
||||
CREATE TYPE pg_temp.tabledefs AS ENUM ('PKEY_INTERNAL','PKEY_EXTERNAL','FKEYS_INTERNAL', 'FKEYS_EXTERNAL', 'COMMENTS', 'FKEYS_NONE', 'INCLUDE_TRIGGERS', 'NO_TRIGGERS');
|
||||
|
||||
-- SELECT * FROM pg_temp.pg_get_coldef('sample','orders','id');
|
||||
-- DROP FUNCTION pg_temp.pg_get_coldef(text,text,text,boolean);
|
||||
CREATE OR REPLACE FUNCTION pg_temp.pg_get_coldef(
|
||||
in_schema text,
|
||||
in_table text,
|
||||
in_column text,
|
||||
oldway boolean default False
|
||||
)
|
||||
RETURNS text
|
||||
LANGUAGE plpgsql VOLATILE
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
v_coldef text;
|
||||
v_dt1 text;
|
||||
v_dt2 text;
|
||||
v_dt3 text;
|
||||
v_nullable boolean;
|
||||
v_position int;
|
||||
v_identity text;
|
||||
v_generated text;
|
||||
v_hasdflt boolean;
|
||||
v_dfltexpr text;
|
||||
|
||||
BEGIN
|
||||
IF oldway THEN
|
||||
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) INTO v_coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t
|
||||
WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum;
|
||||
-- RAISE NOTICE 'DEBUG: oldway=%',v_coldef;
|
||||
ELSE
|
||||
-- a.attrelid::regclass::text, a.attname
|
||||
SELECT CASE WHEN a.atttypid = ANY ('{int,int8,int2}'::regtype[]) AND EXISTS (SELECT FROM pg_attrdef ad WHERE ad.adrelid = a.attrelid AND ad.adnum = a.attnum AND
|
||||
pg_get_expr(ad.adbin, ad.adrelid) = 'nextval(''' || (pg_get_serial_sequence (a.attrelid::regclass::text, a.attname))::regclass || '''::regclass)') THEN CASE a.atttypid
|
||||
WHEN 'int'::regtype THEN 'serial' WHEN 'int8'::regtype THEN 'bigserial' WHEN 'int2'::regtype THEN 'smallserial' END ELSE format_type(a.atttypid, a.atttypmod) END AS data_type
|
||||
INTO v_coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t
|
||||
WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum;
|
||||
-- RAISE NOTICE 'DEBUG: newway=%',v_coldef;
|
||||
|
||||
-- Issue#24: not implemented yet
|
||||
-- might replace with this below to do more detailed parsing...
|
||||
-- SELECT a.atttypid::regtype AS dt1, format_type(a.atttypid, a.atttypmod) as dt2, t.typname as dt3, CASE WHEN not(a.attnotnull) THEN True ELSE False END AS nullable,
|
||||
-- a.attnum, a.attidentity, a.attgenerated, a.atthasdef, pg_get_expr(ad.adbin, ad.adrelid) dfltexpr
|
||||
-- INTO v_dt1, v_dt2, v_dt3, v_nullable, v_position, v_identity, v_generated, v_hasdflt, v_dfltexpr
|
||||
-- FROM pg_attribute a JOIN pg_class c ON (a.attrelid = c.oid) JOIN pg_type t ON (a.atttypid = t.oid) LEFT JOIN pg_attrdef ad ON (a.attrelid = ad.adrelid AND a.attnum = ad.adnum)
|
||||
-- WHERE c.relkind in ('r','p') AND a.attnum > 0 AND NOT a.attisdropped AND c.relnamespace::regnamespace::text = in_schema AND c.relname = in_table AND a.attname = in_column;
|
||||
-- RAISE NOTICE 'schema=% table=% column=% dt1=% dt2=% dt3=% nullable=% pos=% identity=% generated=% HasDefault=% DeftExpr=%', in_schema, in_table, in_column, v_dt1,v_dt2,v_dt3,v_nullable,v_position,v_identity,v_generated,v_hasdflt,v_dfltexpr;
|
||||
END IF;
|
||||
RETURN v_coldef;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- SELECT * FROM pg_temp.pg_get_tabledef('sample', 'address', false);
|
||||
DROP FUNCTION IF EXISTS pg_temp.pg_get_tabledef(character varying,character varying,boolean,tabledefs[]);
|
||||
CREATE OR REPLACE FUNCTION pg_temp.pg_get_tabledef(
|
||||
in_schema varchar,
|
||||
in_table varchar,
|
||||
_verbose boolean,
|
||||
VARIADIC arr pg_temp.tabledefs[] DEFAULT '{}':: pg_temp.tabledefs[]
|
||||
)
|
||||
RETURNS text
|
||||
LANGUAGE plpgsql VOLATILE
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
v_qualified text := '';
|
||||
v_table_ddl text;
|
||||
v_table_oid int;
|
||||
v_colrec record;
|
||||
v_constraintrec record;
|
||||
v_trigrec record;
|
||||
v_indexrec record;
|
||||
v_rec record;
|
||||
v_constraint_name text;
|
||||
v_constraint_def text;
|
||||
v_pkey_def text := '';
|
||||
v_fkey_def text := '';
|
||||
v_fkey_defs text := '';
|
||||
v_trigger text := '';
|
||||
v_partition_key text := '';
|
||||
v_partbound text;
|
||||
v_parent text;
|
||||
v_parent_schema text;
|
||||
v_persist text;
|
||||
v_temp text := '';
|
||||
v_temp2 text;
|
||||
v_relopts text;
|
||||
v_tablespace text;
|
||||
v_pgversion int;
|
||||
bSerial boolean;
|
||||
bPartition boolean;
|
||||
bInheritance boolean;
|
||||
bRelispartition boolean;
|
||||
constraintarr text[] := '{}';
|
||||
constraintelement text;
|
||||
bSkip boolean;
|
||||
bVerbose boolean := False;
|
||||
v_cnt1 integer;
|
||||
v_cnt2 integer;
|
||||
search_path_old text := '';
|
||||
search_path_new text := '';
|
||||
v_partial boolean;
|
||||
v_pos integer;
|
||||
|
||||
-- assume defaults for ENUMs at the getgo
|
||||
pkcnt int := 0;
|
||||
fkcnt int := 0;
|
||||
trigcnt int := 0;
|
||||
cmtcnt int := 0;
|
||||
pktype pg_temp.tabledefs := 'PKEY_INTERNAL';
|
||||
fktype pg_temp.tabledefs := 'FKEYS_INTERNAL';
|
||||
trigtype pg_temp.tabledefs := 'NO_TRIGGERS';
|
||||
arglen integer;
|
||||
vargs text;
|
||||
avarg pg_temp.tabledefs;
|
||||
|
||||
-- exception variables
|
||||
v_ret text;
|
||||
v_diag1 text;
|
||||
v_diag2 text;
|
||||
v_diag3 text;
|
||||
v_diag4 text;
|
||||
v_diag5 text;
|
||||
v_diag6 text;
|
||||
|
||||
BEGIN
|
||||
SET client_min_messages = 'notice';
|
||||
IF _verbose THEN bVerbose = True; END IF;
|
||||
|
||||
-- v17 fix: handle case-sensitive
|
||||
-- v_qualified = in_schema || '.' || in_table;
|
||||
|
||||
arglen := array_length($4, 1);
|
||||
IF arglen IS NULL THEN
|
||||
-- nothing to do, so assume defaults
|
||||
NULL;
|
||||
ELSE
|
||||
-- loop thru args
|
||||
-- IF 'NO_TRIGGERS' = ANY ($4)
|
||||
-- select array_to_string($4, ',', '***') INTO vargs;
|
||||
IF bVerbose THEN RAISE NOTICE 'arguments=%', $4; END IF;
|
||||
FOREACH avarg IN ARRAY $4 LOOP
|
||||
IF bVerbose THEN RAISE NOTICE 'arg=%', avarg; END IF;
|
||||
IF avarg = 'FKEYS_INTERNAL' OR avarg = 'FKEYS_EXTERNAL' OR avarg = 'FKEYS_NONE' THEN
|
||||
fkcnt = fkcnt + 1;
|
||||
fktype = avarg;
|
||||
ELSEIF avarg = 'INCLUDE_TRIGGERS' OR avarg = 'NO_TRIGGERS' THEN
|
||||
trigcnt = trigcnt + 1;
|
||||
trigtype = avarg;
|
||||
ELSEIF avarg = 'PKEY_EXTERNAL' THEN
|
||||
pkcnt = pkcnt + 1;
|
||||
pktype = avarg;
|
||||
ELSEIF avarg = 'COMMENTS' THEN
|
||||
cmtcnt = cmtcnt + 1;
|
||||
|
||||
END IF;
|
||||
END LOOP;
|
||||
IF fkcnt > 1 THEN
|
||||
RAISE WARNING 'Only one foreign key option can be provided. You provided %', fkcnt;
|
||||
RETURN '';
|
||||
ELSEIF trigcnt > 1 THEN
|
||||
RAISE WARNING 'Only one trigger option can be provided. You provided %', trigcnt;
|
||||
RETURN '';
|
||||
ELSEIF pkcnt > 1 THEN
|
||||
RAISE WARNING 'Only one pkey option can be provided. You provided %', pkcnt;
|
||||
RETURN '';
|
||||
ELSEIF cmtcnt > 1 THEN
|
||||
RAISE WARNING 'Only one comments option can be provided. You provided %', cmtcnt;
|
||||
RETURN '';
|
||||
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
SELECT c.oid, (select setting from pg_settings where name = 'server_version_num') INTO v_table_oid, v_pgversion FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relkind in ('r','p') AND c.relname = in_table AND n.nspname = in_schema;
|
||||
|
||||
-- set search_path = public before we do anything to force explicit schema qualification but dont forget to set it back before exiting...
|
||||
SELECT setting INTO search_path_old FROM pg_settings WHERE name = 'search_path';
|
||||
|
||||
-- RAISE NOTICE 'DEBUG tableddl: saving old search_path: ***%***', search_path_old;
|
||||
EXECUTE 'SET search_path = "public"';
|
||||
SELECT setting INTO search_path_new FROM pg_settings WHERE name = 'search_path';
|
||||
-- RAISE NOTICE 'DEBUG tableddl: using new search path=***%***', search_path_new;
|
||||
|
||||
-- throw an error if table was not found
|
||||
IF (v_table_oid IS NULL) THEN
|
||||
RAISE EXCEPTION 'table does not exist';
|
||||
END IF;
|
||||
|
||||
-- get user-defined tablespaces if applicable
|
||||
SELECT tablespace INTO v_temp FROM pg_tables WHERE schemaname = in_schema and tablename = in_table and tablespace IS NOT NULL;
|
||||
IF v_temp IS NULL THEN
|
||||
v_tablespace := 'TABLESPACE pg_default';
|
||||
ELSE
|
||||
v_tablespace := 'TABLESPACE ' || v_temp;
|
||||
END IF;
|
||||
|
||||
-- also see if there are any SET commands for this table, ie, autovacuum_enabled=off, fillfactor=70
|
||||
WITH relopts AS (SELECT unnest(c.reloptions) relopts FROM pg_class c, pg_namespace n WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table)
|
||||
SELECT string_agg(r.relopts, ', ') as relopts INTO v_temp from relopts r;
|
||||
IF v_temp IS NULL THEN
|
||||
v_relopts := '';
|
||||
ELSE
|
||||
v_relopts := ' WITH (' || v_temp || ')';
|
||||
END IF;
|
||||
|
||||
-- -----------------------------------------------------------------------------------
|
||||
-- Create table defs for partitions/children using inheritance or declarative methods.
|
||||
-- inheritance: pg_class.relkind = 'r' pg_class.relispartition=false pg_class.relpartbound is NULL
|
||||
-- declarative: pg_class.relkind = 'r' pg_class.relispartition=true pg_class.relpartbound is NOT NULL
|
||||
-- -----------------------------------------------------------------------------------
|
||||
v_partbound := '';
|
||||
bPartition := False;
|
||||
bInheritance := False;
|
||||
IF v_pgversion < 100000 THEN
|
||||
-- Issue#11: handle parent schema
|
||||
SELECT c2.relname parent, c2.relnamespace::regnamespace INTO v_parent, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2
|
||||
WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r';
|
||||
IF (v_parent IS NOT NULL) THEN
|
||||
bPartition := True;
|
||||
bInheritance := True;
|
||||
END IF;
|
||||
ELSE
|
||||
-- Issue#11: handle parent schema
|
||||
SELECT c2.relname parent, c1.relispartition, pg_get_expr(c1.relpartbound, c1.oid, true), c2.relnamespace::regnamespace INTO v_parent, bRelispartition, v_partbound, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2
|
||||
WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r';
|
||||
IF (v_parent IS NOT NULL) THEN
|
||||
bPartition := True;
|
||||
IF bRelispartition THEN
|
||||
bInheritance := False;
|
||||
ELSE
|
||||
bInheritance := True;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
IF bPartition THEN
|
||||
--Issue#17 fix for case-sensitive tables
|
||||
-- SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s
|
||||
-- WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = quote_ident(in_schema) AND t.table_name = quote_ident(in_table) AND t.table_type = 'BASE TABLE');
|
||||
SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s
|
||||
WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = in_schema AND t.table_name = in_table AND t.table_type = 'BASE TABLE');
|
||||
|
||||
--Issue#19 put double-quotes around SQL keyword column names
|
||||
-- Issue#121: fix keyword lookup for table name not column name that does not apply here
|
||||
-- SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = v_colrec.column_name AND catcode = 'R';
|
||||
SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = in_table AND catcode = 'R';
|
||||
|
||||
IF bInheritance THEN
|
||||
-- inheritance-based
|
||||
IF v_cnt1 > 0 OR v_cnt2 > 0 THEN
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '"( '|| E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || '( '|| E'\\n';
|
||||
END IF;
|
||||
|
||||
-- Jump to constraints section to add the check constraints
|
||||
ELSE
|
||||
-- declarative-based
|
||||
IF v_relopts <> '' THEN
|
||||
IF v_cnt1 > 0 OR v_cnt2 > 0 THEN
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '" PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || v_relopts || ' ' || v_tablespace || '; ' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || v_relopts || ' ' || v_tablespace || '; ' || E'\\n';
|
||||
END IF;
|
||||
ELSE
|
||||
IF v_cnt1 > 0 OR v_cnt2 > 0 THEN
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '" PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || ' ' || v_tablespace || '; ' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || ' ' || v_tablespace || '; ' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
-- Jump to constraints and index section to add the check constraints and indexes and perhaps FKeys
|
||||
END IF;
|
||||
END IF;
|
||||
IF bVerbose THEN RAISE NOTICE '(1)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
IF NOT bPartition THEN
|
||||
-- see if this is unlogged or temporary table
|
||||
select c.relpersistence into v_persist from pg_class c, pg_namespace n where n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r';
|
||||
IF v_persist = 'u' THEN
|
||||
v_temp := 'UNLOGGED';
|
||||
ELSIF v_persist = 't' THEN
|
||||
v_temp := 'TEMPORARY';
|
||||
ELSE
|
||||
v_temp := '';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- start the create definition for regular tables unless we are in progress creating an inheritance-based child table
|
||||
IF NOT bPartition THEN
|
||||
--Issue#17 fix for case-sensitive tables
|
||||
-- SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s
|
||||
-- WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = quote_ident(in_schema) AND t.table_name = quote_ident(in_table) AND t.table_type = 'BASE TABLE');
|
||||
SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s
|
||||
WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = in_schema AND t.table_name = in_table AND t.table_type = 'BASE TABLE');
|
||||
IF v_cnt1 > 0 THEN
|
||||
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '."' || in_table || '" (' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '.' || in_table || ' (' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
-- RAISE NOTICE 'DEBUG2: tabledef so far: %', v_table_ddl;
|
||||
-- define all of the columns in the table unless we are in progress creating an inheritance-based child table
|
||||
IF NOT bPartition THEN
|
||||
FOR v_colrec IN
|
||||
SELECT c.column_name, c.data_type, c.udt_name, c.udt_schema, c.character_maximum_length, c.is_nullable, c.column_default, c.numeric_precision, c.numeric_scale, c.is_identity, c.identity_generation, c.is_generated, c.generation_expression
|
||||
FROM information_schema.columns c WHERE (table_schema, table_name) = (in_schema, in_table) ORDER BY ordinal_position
|
||||
LOOP
|
||||
IF bVerbose THEN RAISE NOTICE '(col loop) name=% type=% udt_name=% default=% is_generated=% gen_expr=%', v_colrec.column_name, v_colrec.data_type, v_colrec.udt_name, v_colrec.column_default, v_colrec.is_generated, v_colrec.generation_expression; END IF;
|
||||
|
||||
-- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling
|
||||
-- SELECT CASE WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL THEN True ELSE False END into bSerial;
|
||||
SELECT CASE WHEN pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL THEN True ELSE False END into bSerial;
|
||||
IF bVerbose THEN
|
||||
-- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling
|
||||
-- SELECT pg_get_serial_sequence(v_qualified, v_colrec.column_name) into v_temp;
|
||||
SELECT pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) into v_temp;
|
||||
IF v_temp IS NULL THEN v_temp = 'NA'; END IF;
|
||||
SELECT pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name) INTO v_diag1;
|
||||
RAISE NOTICE 'DEBUG table: % Column: % datatype: % Serial=% serialval=% coldef=%', v_qualified, v_colrec.column_name, v_colrec.data_type, bSerial, v_temp, v_diag1;
|
||||
RAISE NOTICE 'DEBUG tabledef: %', v_table_ddl;
|
||||
END IF;
|
||||
|
||||
--Issue#17 put double-quotes around case-sensitive column names
|
||||
SELECT COUNT(*) INTO v_cnt1 FROM information_schema.columns t WHERE EXISTS (SELECT REGEXP_MATCHES(s.column_name, '([A-Z]+)','g') FROM information_schema.columns s
|
||||
WHERE t.table_schema=s.table_schema and t.table_name=s.table_name and t.column_name=s.column_name AND t.table_schema = quote_ident(in_schema) AND column_name = v_colrec.column_name);
|
||||
|
||||
--Issue#19 put double-quotes around SQL keyword column names
|
||||
SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = v_colrec.column_name AND catcode = 'R';
|
||||
|
||||
IF v_cnt1 > 0 OR v_cnt2 > 0 THEN
|
||||
v_table_ddl := v_table_ddl || ' "' || v_colrec.column_name || '" ';
|
||||
ELSE
|
||||
v_table_ddl := v_table_ddl || ' ' || v_colrec.column_name || ' ';
|
||||
END IF;
|
||||
|
||||
-- Issue#23: Handle autogenerated columns and rewrite as a simpler IF THEN ELSE branch instead of a much more complex embedded CASE STATEMENT
|
||||
IF v_colrec.is_generated = 'ALWAYS' and v_colrec.generation_expression IS NOT NULL THEN
|
||||
-- searchable tsvector GENERATED ALWAYS AS (to_tsvector('simple'::regconfig, COALESCE(translate(email, '@.-'::citext, ' '::text), ''::text)) ) STORED
|
||||
v_temp = v_colrec.data_type || ' GENERATED ALWAYS AS (' || v_colrec.generation_expression || ') STORED ';
|
||||
ELSEIF v_colrec.udt_name in ('geometry', 'box2d', 'box2df', 'box3d', 'geography', 'geometry_dump', 'gidx', 'spheroid', 'valid_detail') THEN
|
||||
v_temp = v_colrec.udt_name;
|
||||
ELSEIF v_colrec.data_type = 'USER-DEFINED' THEN
|
||||
v_temp = v_colrec.udt_schema || '.' || v_colrec.udt_name;
|
||||
ELSEIF v_colrec.data_type = 'ARRAY' THEN
|
||||
-- Issue#6 fix: handle arrays
|
||||
v_temp = pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name);
|
||||
-- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling
|
||||
-- WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL
|
||||
ELSEIF pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL THEN
|
||||
-- Issue#8 fix: handle serial. Note: NOT NULL is implied so no need to declare it explicitly
|
||||
v_temp = pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name);
|
||||
ELSE
|
||||
v_temp = v_colrec.data_type;
|
||||
END IF;
|
||||
-- RAISE NOTICE 'column def1=%', v_temp;
|
||||
|
||||
-- handle IDENTITY columns
|
||||
IF v_colrec.is_identity = 'YES' THEN
|
||||
IF v_colrec.identity_generation = 'ALWAYS' THEN
|
||||
v_temp = v_temp || ' GENERATED ALWAYS AS IDENTITY';
|
||||
ELSE
|
||||
v_temp = v_temp || ' GENERATED BY DEFAULT AS IDENTITY';
|
||||
END IF;
|
||||
ELSEIF v_colrec.character_maximum_length IS NOT NULL THEN
|
||||
v_temp = v_temp || ('(' || v_colrec.character_maximum_length || ')');
|
||||
ELSEIF v_colrec.numeric_precision > 0 AND v_colrec.numeric_scale > 0 THEN
|
||||
v_temp = v_temp || '(' || v_colrec.numeric_precision || ',' || v_colrec.numeric_scale || ')';
|
||||
END IF;
|
||||
|
||||
-- Handle NULL/NOT NULL
|
||||
IF bSerial THEN
|
||||
v_temp = v_temp || ' NOT NULL';
|
||||
ELSEIF v_colrec.is_nullable = 'NO' THEN
|
||||
v_temp = v_temp || ' NOT NULL';
|
||||
ELSEIF v_colrec.is_nullable = 'YES' THEN
|
||||
v_temp = v_temp || ' NULL';
|
||||
END IF;
|
||||
|
||||
-- Handle defaults
|
||||
IF v_colrec.column_default IS NOT null AND NOT bSerial THEN
|
||||
-- RAISE NOTICE 'Setting default for column, %', v_colrec.column_name;
|
||||
v_temp = v_temp || (' DEFAULT ' || v_colrec.column_default);
|
||||
END IF;
|
||||
v_temp = v_temp || ',' || E'\\n';
|
||||
-- RAISE NOTICE 'column def2=%', v_temp;
|
||||
v_table_ddl := v_table_ddl || v_temp;
|
||||
-- RAISE NOTICE 'tabledef=%', v_table_ddl;
|
||||
|
||||
END LOOP;
|
||||
END IF;
|
||||
IF bVerbose THEN RAISE NOTICE '(2)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- define all the constraints: conparentid does not exist pre PGv11
|
||||
IF v_pgversion < 110000 THEN
|
||||
FOR v_constraintrec IN
|
||||
SELECT con.conname as constraint_name, con.contype as constraint_type,
|
||||
CASE
|
||||
WHEN con.contype = 'p' THEN 1 -- primary key constraint
|
||||
WHEN con.contype = 'u' THEN 2 -- unique constraint
|
||||
WHEN con.contype = 'f' THEN 3 -- foreign key constraint
|
||||
WHEN con.contype = 'c' THEN 4
|
||||
ELSE 5
|
||||
END as type_rank,
|
||||
pg_get_constraintdef(con.oid) as constraint_definition
|
||||
FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
|
||||
WHERE nsp.nspname = in_schema AND rel.relname = in_table ORDER BY type_rank
|
||||
LOOP
|
||||
v_constraint_name := v_constraintrec.constraint_name;
|
||||
v_constraint_def := v_constraintrec.constraint_definition;
|
||||
IF v_constraintrec.type_rank = 1 THEN
|
||||
IF pkcnt = 0 OR pktype = 'PKEY_INTERNAL' THEN
|
||||
-- internal def
|
||||
v_constraint_name := v_constraintrec.constraint_name;
|
||||
v_constraint_def := v_constraintrec.constraint_definition;
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
ELSE
|
||||
-- Issue#16 handle external PG def
|
||||
SELECT 'ALTER TABLE ONLY ' || in_schema || '.' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_pkey_def
|
||||
FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table and r.conname = v_constraint_name;
|
||||
END IF;
|
||||
IF bPartition THEN
|
||||
continue;
|
||||
END IF;
|
||||
ELSIF v_constraintrec.type_rank = 3 THEN
|
||||
-- handle foreign key constraints
|
||||
--Issue#22 fix: added FKEY_NONE check
|
||||
IF fktype = 'FKEYS_NONE' THEN
|
||||
-- skip
|
||||
continue;
|
||||
ELSIF fkcnt = 0 OR fktype = 'FKEYS_INTERNAL' THEN
|
||||
-- internal def
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
ELSE
|
||||
-- external def
|
||||
SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_fkey_def
|
||||
FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table;
|
||||
v_fkey_defs = v_fkey_defs || v_fkey_def || E'\\n';
|
||||
END IF;
|
||||
ELSE
|
||||
-- handle all other constraints besides PKEY and FKEYS as internal defs by default
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
END IF;
|
||||
if bVerbose THEN RAISE NOTICE 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF;
|
||||
constraintarr := constraintarr || v_constraintrec.constraint_name:: text;
|
||||
|
||||
END LOOP;
|
||||
ELSE
|
||||
-- handle PG versions 11 and up
|
||||
-- Issue#20: Fix logic for external PKEY and FKEYS
|
||||
FOR v_constraintrec IN
|
||||
SELECT con.conname as constraint_name, con.contype as constraint_type,
|
||||
CASE
|
||||
WHEN con.contype = 'p' THEN 1 -- primary key constraint
|
||||
WHEN con.contype = 'u' THEN 2 -- unique constraint
|
||||
WHEN con.contype = 'f' THEN 3 -- foreign key constraint
|
||||
WHEN con.contype = 'c' THEN 4
|
||||
ELSE 5
|
||||
END as type_rank,
|
||||
pg_get_constraintdef(con.oid) as constraint_definition
|
||||
FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
|
||||
WHERE nsp.nspname = in_schema AND rel.relname = in_table
|
||||
--Issue#13 added this condition:
|
||||
AND con.conparentid = 0
|
||||
ORDER BY type_rank
|
||||
LOOP
|
||||
v_constraint_name := v_constraintrec.constraint_name;
|
||||
v_constraint_def := v_constraintrec.constraint_definition;
|
||||
IF v_constraintrec.type_rank = 1 THEN
|
||||
IF pkcnt = 0 OR pktype = 'PKEY_INTERNAL' THEN
|
||||
-- internal def
|
||||
v_constraint_name := v_constraintrec.constraint_name;
|
||||
v_constraint_def := v_constraintrec.constraint_definition;
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
ELSE
|
||||
-- Issue#16 handle external PG def
|
||||
SELECT 'ALTER TABLE ONLY ' || in_schema || '.' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_pkey_def
|
||||
FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table;
|
||||
END IF;
|
||||
IF bPartition THEN
|
||||
continue;
|
||||
END IF;
|
||||
ELSIF v_constraintrec.type_rank = 3 THEN
|
||||
-- handle foreign key constraints
|
||||
--Issue#22 fix: added FKEY_NONE check
|
||||
IF fktype = 'FKEYS_NONE' THEN
|
||||
-- skip
|
||||
continue;
|
||||
ELSIF fkcnt = 0 OR fktype = 'FKEYS_INTERNAL' THEN
|
||||
-- internal def
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
ELSE
|
||||
-- external def
|
||||
SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_fkey_def
|
||||
FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table and
|
||||
r.conname = v_constraint_name and r.conparentid = 0;
|
||||
v_fkey_defs = v_fkey_defs || v_fkey_def || E'\\n';
|
||||
END IF;
|
||||
ELSE
|
||||
-- handle all other constraints besides PKEY and FKEYS as internal defs by default
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
END IF;
|
||||
if bVerbose THEN RAISE NOTICE 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF;
|
||||
constraintarr := constraintarr || v_constraintrec.constraint_name:: text;
|
||||
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- drop the last comma before ending the create statement, which should be right before the carriage return character
|
||||
-- Issue#24: make sure the comma is there before removing it
|
||||
select substring(v_table_ddl, length(v_table_ddl) - 1, 1) INTO v_temp;
|
||||
IF v_temp = ',' THEN
|
||||
v_table_ddl = substr(v_table_ddl, 0, length(v_table_ddl) - 1) || E'\\n';
|
||||
END IF;
|
||||
IF bVerbose THEN RAISE NOTICE '(3)tabledef so far: %', trim(v_table_ddl); END IF;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- at this point we have everything up to the last table-enclosing parenthesis
|
||||
-- ---------------------------------------------------------------------------
|
||||
IF bVerbose THEN RAISE NOTICE '(4)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- See if this is an inheritance-based child table and finish up the table create.
|
||||
IF bPartition and bInheritance THEN
|
||||
-- Issue#11: handle parent schema
|
||||
-- v_table_ddl := v_table_ddl || ') INHERITS (' || in_schema || '.' || v_parent || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
|
||||
IF v_parent_schema = '' OR v_parent_schema IS NULL THEN v_parent_schema = in_schema; END IF;
|
||||
v_table_ddl := v_table_ddl || ') INHERITS (' || v_parent_schema || '.' || v_parent || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
|
||||
END IF;
|
||||
|
||||
IF v_pgversion >= 100000 AND NOT bPartition and NOT bInheritance THEN
|
||||
-- See if this is a partitioned table (pg_class.relkind = 'p') and add the partitioned key
|
||||
SELECT pg_get_partkeydef(c1.oid) as partition_key INTO v_partition_key FROM pg_class c1 JOIN pg_namespace n ON (n.oid = c1.relnamespace) LEFT JOIN pg_partitioned_table p ON (c1.oid = p.partrelid)
|
||||
WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.relkind = 'p';
|
||||
|
||||
IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN
|
||||
-- add partition clause
|
||||
-- NOTE: cannot specify default tablespace for partitioned relations
|
||||
-- v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ' ' || v_tablespace || ';' || E'\\n';
|
||||
v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ';' || E'\\n';
|
||||
ELSEIF v_relopts <> '' THEN
|
||||
v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
|
||||
ELSE
|
||||
-- end the create definition
|
||||
v_table_ddl := v_table_ddl || ') ' || v_tablespace || ';' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF bVerbose THEN RAISE NOTICE '(5)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- Add closing paren for regular tables
|
||||
-- IF NOT bPartition THEN
|
||||
-- v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || E';\\n';
|
||||
-- END IF;
|
||||
-- RAISE NOTICE 'ddlsofar3: %', v_table_ddl;
|
||||
|
||||
-- Issue#16 create the external PKEY def if indicated
|
||||
IF v_pkey_def <> '' THEN
|
||||
v_table_ddl := v_table_ddl || v_pkey_def || E'\\n';
|
||||
END IF;
|
||||
|
||||
-- Issue#20
|
||||
IF v_fkey_defs <> '' THEN
|
||||
v_table_ddl := v_table_ddl || v_fkey_defs || E'\\n';
|
||||
END IF;
|
||||
|
||||
IF bVerbose THEN RAISE NOTICE '(6)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- create indexes
|
||||
FOR v_indexrec IN
|
||||
SELECT indexdef, COALESCE(tablespace, 'pg_default') as tablespace, indexname FROM pg_indexes WHERE (schemaname, tablename) = (in_schema, in_table)
|
||||
LOOP
|
||||
-- RAISE NOTICE 'DEBUG6: indexname=% indexdef=%', v_indexrec.indexname, v_indexrec.indexdef;
|
||||
-- loop through constraints and skip ones already defined
|
||||
bSkip = False;
|
||||
FOREACH constraintelement IN ARRAY constraintarr
|
||||
LOOP
|
||||
IF constraintelement = v_indexrec.indexname THEN
|
||||
-- RAISE NOTICE 'DEBUG7: skipping index, %', v_indexrec.indexname;
|
||||
bSkip = True;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
if bSkip THEN CONTINUE; END IF;
|
||||
|
||||
-- Add IF NOT EXISTS clause so partition index additions will not be created if declarative partition in effect and index already created on parent
|
||||
v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE INDEX', 'CREATE INDEX IF NOT EXISTS');
|
||||
-- Fix Issue#26: do it for unique/primary key indexes as well
|
||||
v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE UNIQUE INDEX', 'CREATE UNIQUE INDEX IF NOT EXISTS');
|
||||
-- RAISE NOTICE 'DEBUG8: adding index, %', v_indexrec.indexname;
|
||||
|
||||
-- NOTE: cannot specify default tablespace for partitioned relations
|
||||
IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN
|
||||
v_table_ddl := v_table_ddl || v_indexrec.indexdef || ';' || E'\\n';
|
||||
ELSE
|
||||
-- Issue#25: see if partial index or not
|
||||
select CASE WHEN i.indpred IS NOT NULL THEN True ELSE False END INTO v_partial
|
||||
FROM pg_index i JOIN pg_class c1 ON (i.indexrelid = c1.oid) JOIN pg_class c2 ON (i.indrelid = c2.oid)
|
||||
WHERE c1.relnamespace::regnamespace::text = in_schema AND c2.relnamespace::regnamespace::text = in_schema AND c2.relname = in_table AND c1.relname = v_indexrec.indexname;
|
||||
IF v_partial THEN
|
||||
-- Put tablespace def before WHERE CLAUSE
|
||||
v_temp = v_indexrec.indexdef;
|
||||
v_pos = POSITION(' WHERE ' IN v_temp);
|
||||
v_temp2 = SUBSTRING(v_temp, v_pos);
|
||||
v_temp = SUBSTRING(v_temp, 1, v_pos);
|
||||
v_table_ddl := v_table_ddl || v_temp || ' TABLESPACE ' || v_indexrec.tablespace || v_temp2 || ';' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := v_table_ddl || v_indexrec.indexdef || ' TABLESPACE ' || v_indexrec.tablespace || ';' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
END LOOP;
|
||||
IF bVerbose THEN RAISE NOTICE '(7)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- Issue#20: added logic for table and column comments
|
||||
IF cmtcnt > 0 THEN
|
||||
FOR v_rec IN
|
||||
SELECT c.relname, 'COMMENT ON ' || CASE WHEN c.relkind in ('r','p') AND a.attname IS NULL THEN 'TABLE ' WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN 'COLUMN ' WHEN c.relkind = 'f' THEN 'FOREIGN TABLE '
|
||||
WHEN c.relkind = 'm' THEN 'MATERIALIZED VIEW ' WHEN c.relkind = 'v' THEN 'VIEW ' WHEN c.relkind = 'i' THEN 'INDEX ' WHEN c.relkind = 'S' THEN 'SEQUENCE ' ELSE 'XX' END || n.nspname || '.' ||
|
||||
CASE WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN quote_ident(c.relname) || '.' || a.attname ELSE quote_ident(c.relname) END || ' IS ' || quote_literal(d.description) || ';' as ddl
|
||||
FROM pg_class c JOIN pg_namespace n ON (n.oid = c.relnamespace) LEFT JOIN pg_description d ON (c.oid = d.objoid) LEFT JOIN pg_attribute a ON (c.oid = a.attrelid AND a.attnum > 0 and a.attnum = d.objsubid)
|
||||
WHERE d.description IS NOT NULL AND n.nspname = in_schema AND c.relname = in_table ORDER BY 2 desc, ddl
|
||||
LOOP
|
||||
--RAISE NOTICE 'comments:%', v_rec.ddl;
|
||||
v_table_ddl = v_table_ddl || v_rec.ddl || E'\\n';
|
||||
END LOOP;
|
||||
END IF;
|
||||
IF bVerbose THEN RAISE NOTICE '(8)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
IF trigtype = 'INCLUDE_TRIGGERS' THEN
|
||||
-- Issue#14: handle multiple triggers for a table
|
||||
FOR v_trigrec IN
|
||||
select pg_get_triggerdef(t.oid, True) || ';' as triggerdef FROM pg_trigger t, pg_class c, pg_namespace n
|
||||
WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r' and t.tgrelid = c.oid and NOT t.tgisinternal
|
||||
LOOP
|
||||
v_table_ddl := v_table_ddl || v_trigrec.triggerdef;
|
||||
v_table_ddl := v_table_ddl || E'\\n';
|
||||
IF bVerbose THEN RAISE NOTICE 'triggerdef = %', v_trigrec.triggerdef; END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
IF bVerbose THEN RAISE NOTICE '(9)tabledef so far: %', v_table_ddl; END IF;
|
||||
-- add empty line
|
||||
v_table_ddl := v_table_ddl || E'\\n';
|
||||
IF bVerbose THEN RAISE NOTICE '(10)tabledef so far: %', v_table_ddl; END IF;
|
||||
|
||||
-- reset search_path back to what it was
|
||||
IF search_path_old = '' THEN
|
||||
SELECT set_config('search_path', '', false) into v_temp;
|
||||
ELSE
|
||||
EXECUTE 'SET search_path = ' || search_path_old;
|
||||
END IF;
|
||||
|
||||
RETURN v_table_ddl;
|
||||
|
||||
EXCEPTION
|
||||
WHEN others THEN
|
||||
BEGIN
|
||||
GET STACKED DIAGNOSTICS v_diag1 = MESSAGE_TEXT, v_diag2 = PG_EXCEPTION_DETAIL, v_diag3 = PG_EXCEPTION_HINT, v_diag4 = RETURNED_SQLSTATE, v_diag5 = PG_CONTEXT, v_diag6 = PG_EXCEPTION_CONTEXT;
|
||||
-- v_ret := 'line=' || v_diag6 || '. '|| v_diag4 || '. ' || v_diag1 || ' .' || v_diag2 || ' .' || v_diag3;
|
||||
v_ret := 'line=' || v_diag6 || '. '|| v_diag4 || '. ' || v_diag1;
|
||||
RAISE EXCEPTION '%', v_ret;
|
||||
-- put additional coding here if necessarY
|
||||
RETURN '';
|
||||
END;
|
||||
|
||||
END;
|
||||
$$;`.trim()
|
||||
|
||||
export const getTableDefinitionSql = ({ id }: { id: number }) => {
|
||||
const sql = /* SQL */ `
|
||||
${CREATE_PG_GET_TABLEDEF_SQL}
|
||||
|
||||
with table_info as (
|
||||
select
|
||||
n.nspname::text as schema,
|
||||
c.relname::text as name
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.oid = ${id}
|
||||
)
|
||||
select pg_temp.pg_get_tabledef (
|
||||
t.schema,
|
||||
t.name,
|
||||
false,
|
||||
'FKEYS_INTERNAL',
|
||||
'INCLUDE_TRIGGERS'
|
||||
) as definition
|
||||
from table_info t;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export const getEntityDefinitionsSql = ({
|
||||
schemas,
|
||||
limit = 100,
|
||||
}: {
|
||||
schemas: string[]
|
||||
limit?: number
|
||||
}) => {
|
||||
const sql = /* SQL */ `
|
||||
${CREATE_PG_GET_TABLEDEF_SQL}
|
||||
|
||||
with records as (
|
||||
select
|
||||
c.oid::int8 as "id",
|
||||
case c.relkind
|
||||
when 'r' then pg_temp.pg_get_tabledef(
|
||||
concat(nc.nspname),
|
||||
concat(c.relname),
|
||||
false,
|
||||
'FKEYS_INTERNAL',
|
||||
'NO_TRIGGERS'
|
||||
)
|
||||
when 'v' then concat(
|
||||
'create view ', concat(nc.nspname, '.', c.relname), ' as',
|
||||
pg_get_viewdef(concat(nc.nspname, '.', c.relname), true)
|
||||
)
|
||||
when 'm' then concat(
|
||||
'create materialized view ', concat(nc.nspname, '.', c.relname), ' as',
|
||||
pg_get_viewdef(concat(nc.nspname, '.', c.relname), true)
|
||||
)
|
||||
when 'f' then concat('create foreign table ', nc.nspname, '.', c.relname, ' ( ... )')
|
||||
when 'p' then pg_temp.pg_get_tabledef(
|
||||
concat(nc.nspname),
|
||||
concat(c.relname),
|
||||
false,
|
||||
'FKEYS_INTERNAL',
|
||||
'NO_TRIGGERS'
|
||||
)
|
||||
end as "sql"
|
||||
from
|
||||
pg_namespace nc
|
||||
join pg_class c on nc.oid = c.relnamespace
|
||||
where
|
||||
c.relkind in ('r', 'v', 'm', 'f', 'p')
|
||||
and not pg_is_other_temp_schema(nc.oid)
|
||||
and (
|
||||
pg_has_role(c.relowner, 'USAGE')
|
||||
or has_table_privilege(
|
||||
c.oid,
|
||||
'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
|
||||
)
|
||||
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
and nc.nspname IN (${schemas.map((schema) => `'${schema}'`).join(', ')})
|
||||
order by c.relname asc
|
||||
limit ${limit}
|
||||
offset 0
|
||||
)
|
||||
select
|
||||
jsonb_build_object(
|
||||
'definitions', coalesce(jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', r.id,
|
||||
'sql', r.sql
|
||||
)
|
||||
), '[]'::jsonb)
|
||||
) "data"
|
||||
from records r;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ident } from '../../../pg-format'
|
||||
import { PGTrigger, PGTriggerCreate } from '../../../pg-meta-triggers'
|
||||
|
||||
// [Joshen] Writing this query within FE as the PATCH endpoint from pg-meta only supports updating
|
||||
// trigger name and enabled mode. So we'll delete and create the trigger, within a single transaction
|
||||
// Copying the SQL from https://github.com/supabase/postgres-meta/blob/master/src/lib/PostgresMetaTriggers.ts
|
||||
export function getDatabaseTriggerUpdateSQL({
|
||||
originalTrigger,
|
||||
updatedTrigger,
|
||||
}: {
|
||||
originalTrigger: {
|
||||
id: number
|
||||
table_id: number
|
||||
enabled_mode: 'ORIGIN' | 'REPLICA' | 'ALWAYS' | 'DISABLED'
|
||||
name: string
|
||||
table: string
|
||||
schema: string
|
||||
condition: string | null
|
||||
orientation: 'ROW' | 'STATEMENT'
|
||||
activation: 'BEFORE' | 'AFTER' | 'INSTEAD OF'
|
||||
events: string[]
|
||||
function_schema: string
|
||||
function_name: string
|
||||
function_args: string[]
|
||||
}
|
||||
updatedTrigger: PGTriggerCreate & Pick<PGTrigger, 'enabled_mode'>
|
||||
}) {
|
||||
const { name, activation, events, schema, table, function_schema, function_name, function_args } =
|
||||
updatedTrigger
|
||||
return /* SQL */ `
|
||||
BEGIN;
|
||||
DROP TRIGGER "${originalTrigger.name}" ON "${originalTrigger.schema}"."${originalTrigger.table}";
|
||||
CREATE TRIGGER "${name}" ${activation} ${events.join(' OR ')} ON "${schema}"."${table}"
|
||||
FOR EACH ROW EXECUTE FUNCTION
|
||||
"${function_schema}"."${function_name}"(${function_args?.map(ident).join(',') ?? ''});
|
||||
COMMIT;
|
||||
`.trim()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export const getViewDefinitionSql = ({ id }: { id: number }) => {
|
||||
if (!id) {
|
||||
throw new Error('id is required')
|
||||
}
|
||||
|
||||
const sql = /* SQL */ `
|
||||
with table_info as (
|
||||
select
|
||||
n.nspname::text as schema,
|
||||
c.relname::text as name,
|
||||
to_regclass(concat('"', n.nspname, '"."', c.relname, '"')) as regclass
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.oid = ${id}
|
||||
)
|
||||
select pg_get_viewdef(t.regclass, true) as definition
|
||||
from table_info t
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { literal } from '../../pg-format'
|
||||
|
||||
export const getLiveTupleEstimate = (table: string, schema: string = 'public') => {
|
||||
const sql = /* SQL */ `
|
||||
SELECT n_live_tup AS live_tuple_estimate
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname = ${literal(schema)}
|
||||
AND relname = ${literal(table)};
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export const getAbortQuerySQL = ({ pid }: { pid: number }) => {
|
||||
return `select pg_terminate_backend(${pid})`.trim()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './abort-query'
|
||||
export * from './keywords'
|
||||
export * from './ongoing-queries'
|
||||
@@ -0,0 +1,7 @@
|
||||
export const getKeywordsSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
SELECT word FROM pg_get_keywords();
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export const getOngoingQueriesSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
select pid, query, query_start from pg_stat_activity where state = 'active' and datname = 'postgres';
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
@@ -1,5 +1,33 @@
|
||||
import { ident } from '../../../pg-format'
|
||||
|
||||
export const getTableConstraintsSql = ({ id }: { id: number }) => {
|
||||
const sql = /* SQL */ `
|
||||
with table_info as (
|
||||
select
|
||||
n.nspname::text as schema,
|
||||
c.relname::text as name,
|
||||
to_regclass(concat('"', n.nspname, '"."', c.relname, '"')) as regclass
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.oid = ${id}
|
||||
)
|
||||
select
|
||||
con.oid as id,
|
||||
con.conname as name,
|
||||
con.contype as type
|
||||
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
|
||||
inner join table_info ti
|
||||
on ti.schema = nsp.nspname
|
||||
and ti.name = rel.relname;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export const getDropConstraintSQL = ({
|
||||
schema,
|
||||
table,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
export const getEntityTypesSQL = ({
|
||||
schemas,
|
||||
search,
|
||||
sort,
|
||||
filterTypes,
|
||||
limit,
|
||||
page,
|
||||
}: {
|
||||
schemas: string[]
|
||||
search?: string
|
||||
sort: 'alphabetical' | 'grouped-alphabetical'
|
||||
filterTypes: string[]
|
||||
limit: number
|
||||
page: number
|
||||
}) => {
|
||||
const innerOrderBy = sort === 'alphabetical' ? `c.relname asc` : `"type_sort" asc, c.relname asc`
|
||||
const outerOrderBy = sort === 'alphabetical' ? `r.name asc` : `r.type_sort asc, r.name asc`
|
||||
|
||||
const sql = /* SQL */ `
|
||||
with records as (
|
||||
select
|
||||
c.oid::int8 as "id",
|
||||
nc.nspname as "schema",
|
||||
c.relname as "name",
|
||||
c.relkind as "type",
|
||||
case c.relkind
|
||||
when 'r' then 1
|
||||
when 'v' then 2
|
||||
when 'm' then 3
|
||||
when 'f' then 4
|
||||
when 'p' then 5
|
||||
end as "type_sort",
|
||||
obj_description(c.oid) as "comment",
|
||||
count(*) over() as "count",
|
||||
c.relrowsecurity as "rls_enabled"
|
||||
from
|
||||
pg_namespace nc
|
||||
join pg_class c on nc.oid = c.relnamespace
|
||||
where
|
||||
c.relkind in (${filterTypes.map((x) => `'${x}'`).join(', ')})
|
||||
and not pg_is_other_temp_schema(nc.oid)
|
||||
and (
|
||||
pg_has_role(c.relowner, 'USAGE')
|
||||
or has_table_privilege(
|
||||
c.oid,
|
||||
'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
|
||||
)
|
||||
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
and nc.nspname in (${schemas.map((x) => `'${x}'`)})
|
||||
${search ? `and c.relname ilike '%${search}%'` : ''}
|
||||
order by ${innerOrderBy}
|
||||
limit ${limit}
|
||||
offset ${page * limit}
|
||||
)
|
||||
select
|
||||
jsonb_build_object(
|
||||
'entities', coalesce(jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', r.id,
|
||||
'schema', r.schema,
|
||||
'name', r.name,
|
||||
'type', r.type,
|
||||
'comment', r.comment,
|
||||
'rls_enabled', r.rls_enabled
|
||||
)
|
||||
order by ${outerOrderBy}
|
||||
), '[]'::jsonb),
|
||||
'count', coalesce(min(r.count), 0)
|
||||
) "data"
|
||||
from records r;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
@@ -64,6 +64,61 @@ DROP CONSTRAINT IF EXISTS ${ident(relation.name)}
|
||||
)
|
||||
}
|
||||
|
||||
export const getForeignKeyConstraintsSql = ({ schema }: { schema: string }) => {
|
||||
if (!schema) throw new Error('schema is required')
|
||||
|
||||
const sql = /* SQL */ `
|
||||
SELECT
|
||||
con.oid as id,
|
||||
con.conname as constraint_name,
|
||||
con.confdeltype as deletion_action,
|
||||
con.confupdtype as update_action,
|
||||
rel.oid as source_id,
|
||||
nsp.nspname as source_schema,
|
||||
rel.relname as source_table,
|
||||
(
|
||||
SELECT
|
||||
array_agg(
|
||||
att.attname
|
||||
ORDER BY
|
||||
un.ord
|
||||
)
|
||||
FROM
|
||||
unnest(con.conkey) WITH ORDINALITY un (attnum, ord)
|
||||
INNER JOIN pg_attribute att ON att.attnum = un.attnum
|
||||
WHERE
|
||||
att.attrelid = rel.oid
|
||||
) source_columns,
|
||||
frel.oid as target_id,
|
||||
fnsp.nspname as target_schema,
|
||||
frel.relname as target_table,
|
||||
(
|
||||
SELECT
|
||||
array_agg(
|
||||
att.attname
|
||||
ORDER BY
|
||||
un.ord
|
||||
)
|
||||
FROM
|
||||
unnest(con.confkey) WITH ORDINALITY un (attnum, ord)
|
||||
INNER JOIN pg_attribute att ON att.attnum = un.attnum
|
||||
WHERE
|
||||
att.attrelid = frel.oid
|
||||
) target_columns
|
||||
FROM
|
||||
pg_constraint con
|
||||
INNER JOIN pg_class rel ON rel.oid = con.conrelid
|
||||
INNER JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
|
||||
INNER JOIN pg_class frel ON frel.oid = con.confrelid
|
||||
INNER JOIN pg_namespace fnsp ON fnsp.oid = frel.relnamespace
|
||||
WHERE
|
||||
con.contype = 'f'
|
||||
AND nsp.nspname = '${schema}'
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export interface ForeignKey {
|
||||
id?: number | string
|
||||
name?: string
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './foreign-keys'
|
||||
export * from './primary-keys'
|
||||
export * from './rls'
|
||||
export * from './table'
|
||||
export * from './entities'
|
||||
Reference in new issue
Block a user