mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 22:59:11 +03:00
getTableDefinitionSql to escape SQL identifiers (#51258)
## Context Similar to domain to https://github.com/supabase/supabase/pull/51256 - `getTableDefinitionSql` doesn't escape SQL identifiers, which generates invalid SQL on the dashboard's table editor for the "Copy table schema" CTA, or the table definition tab. Also fixes the "Copy table schema" CTA which was missing the `scoped` parameter when calling `getTableDefinition` Changes here addresses this issue, can test with a table named like `test"table`
This commit is contained in:
1 parent
1da25a7e72
commit
eb20a4674d
12 files changed
+110
-40
No files matched your search
@@ -72,7 +72,7 @@ export const EditHookPanel = () => {
|
||||
const { ref } = useParams()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const [isLoadingTable, setIsLoadingTable] = useState(false)
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
const scoped = useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
const { data: hooks = [], isSuccess } = useDatabaseHooksQuery({
|
||||
projectRef: project?.ref,
|
||||
|
||||
+1
-1
@@ -193,7 +193,7 @@ export const SidePanelEditor = ({
|
||||
const isQueueOperationsEnabled = useIsQueueOperationsEnabled()
|
||||
const { updateRow, addRow, isEditPending } = useTableRowOperations()
|
||||
const getImpersonatedRoleState = useGetImpersonatedRoleState()
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
const scoped = useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
const [isEdited, setIsEdited] = useState<boolean>(false)
|
||||
const csvImportKey = useVisibleKey(snap.sidePanel?.type === 'csv-import')
|
||||
|
||||
@@ -97,7 +97,7 @@ export const UnifiedLogs = () => {
|
||||
const [search, setSearch] = useQueryStates(SEARCH_PARAMS_PARSER)
|
||||
const showMultigresLogs = useShowMultigresLogs()
|
||||
const { hasLoaded: flagsLoaded } = useFeatureFlags()
|
||||
const computeEnabled = !!useFlag('compute')
|
||||
const computeEnabled = useFlag('compute')
|
||||
const computeAvailability = getComputeLogsAvailability({
|
||||
isPlatform: IS_PLATFORM,
|
||||
flagsLoaded,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useParams } from 'common'
|
||||
import { useFlag, useParams } from 'common'
|
||||
import { Copy, Download, Edit, Globe, Lock, MoreVertical, Trash } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { type CSSProperties } from 'react'
|
||||
@@ -38,6 +38,7 @@ import type {
|
||||
TableApiAccessData,
|
||||
TableApiAccessMap,
|
||||
} from '@/data/privileges/table-api-access-query'
|
||||
import { PG_META_SCOPED_INTROSPECTION_FLAG } from '@/data/table-editor/table-editor-query'
|
||||
import { useTableRowsCountQuery } from '@/data/table-rows/table-rows-count-query'
|
||||
import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
@@ -78,6 +79,7 @@ export const EntityListItem = ({
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const snap = useTableEditorStateSnapshot()
|
||||
const { selectedSchema } = useQuerySchemaState()
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
const tabId = createTabId(entity.type, { id: entity.id })
|
||||
const tabs = useTabsStateSnapshot()
|
||||
@@ -276,6 +278,7 @@ export const EntityListItem = ({
|
||||
id: entity.id,
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
scoped,
|
||||
}).then((tableDefinition) => {
|
||||
if (!tableDefinition) {
|
||||
throw new Error('Failed to get table schema')
|
||||
|
||||
@@ -47,7 +47,7 @@ export const useTableDefinitionQuery = <TData = TableDefinitionData>(
|
||||
...options
|
||||
}: UseCustomQueryOptions<TableDefinitionData, TableDefinitionError, TData> = {}
|
||||
) => {
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
const scoped = useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
return useQuery<TableDefinitionData, TableDefinitionError, TData>({
|
||||
queryKey: [...databaseKeys.tableDefinition(projectRef, id), { scoped }],
|
||||
|
||||
@@ -76,7 +76,7 @@ export function usePrefetchEditorTablePage() {
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const { connectionString, identifier: readReplicaIdentifier } = useConnectionStringForReadOps()
|
||||
const roleImpersonationState = useRoleImpersonationStateSnapshot()
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
const scoped = useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
return useCallback(
|
||||
({ id: _id, filters, sorts }: { id?: string; filters?: Filter[]; sorts?: Sort[] }) => {
|
||||
|
||||
@@ -58,7 +58,7 @@ export const scopedIntrospectionReady = (): Promise<void> => {
|
||||
*/
|
||||
export const useSyncScopedIntrospection = () => {
|
||||
const { hasLoaded } = useFeatureFlags()
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
const scoped = useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
useEffect(() => {
|
||||
setScopedIntrospection(scoped)
|
||||
|
||||
@@ -51,7 +51,7 @@ export const useTableEditorQuery = <TData = TableEditorData>(
|
||||
...options
|
||||
}: UseCustomQueryOptions<TableEditorData, TableEditorError, TData> = {}
|
||||
) => {
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
const scoped = useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
return useQuery<TableEditorData, TableEditorError, TData>({
|
||||
...tableEditorQueryOptions({ projectRef, connectionString, id, scoped }),
|
||||
|
||||
@@ -116,7 +116,7 @@ export const useTableRowsCountQuery = <TData = TableRowsCountData>(
|
||||
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
||||
'tables'
|
||||
)
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
const scoped = useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
return useQuery<TableRowsCountData, TableRowsCountError, TData>({
|
||||
queryKey: tableRowKeys.tableRowsCount(projectRef, {
|
||||
|
||||
@@ -403,7 +403,7 @@ export const useTableRowsQuery = <TData = TableRowsData>(
|
||||
) => {
|
||||
const queryClient = useQueryClient()
|
||||
const { connectionString, identifier: readReplicaIdentifier } = useConnectionStringForReadOps()
|
||||
const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
const scoped = useFlag(PG_META_SCOPED_INTROSPECTION_FLAG)
|
||||
|
||||
// [Ali] Exclude preflightCheck from query key — it controls how the query
|
||||
// executes (whether an EXPLAIN guard runs first), not what data is returned.
|
||||
|
||||
@@ -941,16 +941,16 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
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';
|
||||
SELECT c2.relname parent, n2.nspname INTO v_parent, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2, pg_namespace n2
|
||||
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' and n2.oid = c2.relnamespace;
|
||||
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';
|
||||
SELECT c2.relname parent, c1.relispartition, pg_get_expr(c1.relpartbound, c1.oid, true), n2.nspname INTO v_parent, bRelispartition, v_partbound, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2, pg_namespace n2
|
||||
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' and n2.oid = c2.relnamespace;
|
||||
IF (v_parent IS NOT NULL) THEN
|
||||
bPartition := True;
|
||||
IF bRelispartition THEN
|
||||
@@ -977,9 +977,9 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
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';
|
||||
v_table_ddl := 'CREATE TABLE ' || quote_ident(in_schema) || '.' || quote_ident(in_table) || '( '|| E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || '( '|| E'\\n';
|
||||
v_table_ddl := 'CREATE TABLE ' || quote_ident(in_schema) || '.' || quote_ident(in_table) || '( '|| E'\\n';
|
||||
END IF;
|
||||
|
||||
-- Jump to constraints section to add the check constraints
|
||||
@@ -987,15 +987,15 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
-- 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';
|
||||
v_table_ddl := 'CREATE TABLE ' || quote_ident(in_schema) || '.' || quote_ident(in_table) || ' PARTITION OF ' || quote_ident(in_schema) || '.' || quote_ident(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';
|
||||
v_table_ddl := 'CREATE TABLE ' || quote_ident(in_schema) || '.' || quote_ident(in_table) || ' PARTITION OF ' || quote_ident(in_schema) || '.' || quote_ident(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';
|
||||
v_table_ddl := 'CREATE TABLE ' || quote_ident(in_schema) || '.' || quote_ident(in_table) || ' PARTITION OF ' || quote_ident(in_schema) || '.' || quote_ident(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';
|
||||
v_table_ddl := 'CREATE TABLE ' || quote_ident(in_schema) || '.' || quote_ident(in_table) || ' PARTITION OF ' || quote_ident(in_schema) || '.' || quote_ident(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
|
||||
@@ -1024,9 +1024,9 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
-- 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');
|
||||
v_cnt1 := CASE WHEN in_table ~ '[A-Z]' THEN 1 ELSE 0 END;
|
||||
IF v_cnt1 > 0 THEN
|
||||
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '."' || in_table || '" (' || E'\\n';
|
||||
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || quote_ident(in_schema) || '.' || quote_ident(in_table) || ' (' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '.' || in_table || ' (' || E'\\n';
|
||||
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || quote_ident(in_schema) || '.' || quote_ident(in_table) || ' (' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
-- RAISE NOTICE 'DEBUG2: tabledef so far: %', v_table_ddl;
|
||||
@@ -1066,9 +1066,9 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
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 || '" ';
|
||||
v_table_ddl := v_table_ddl || ' ' || quote_ident(v_colrec.column_name) || ' ';
|
||||
ELSE
|
||||
v_table_ddl := v_table_ddl || ' ' || v_colrec.column_name || ' ';
|
||||
v_table_ddl := v_table_ddl || ' ' || quote_ident(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
|
||||
@@ -1078,7 +1078,7 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
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;
|
||||
v_temp = quote_ident(v_colrec.udt_schema) || '.' || quote_ident(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);
|
||||
@@ -1152,12 +1152,12 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
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 || ' '
|
||||
|| quote_ident(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
|
||||
SELECT 'ALTER TABLE ONLY ' || quote_ident(in_schema) || '.' || quote_ident(c.relname) || ' ADD CONSTRAINT ' || quote_ident(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
|
||||
@@ -1173,12 +1173,12 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
-- internal def
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| quote_ident(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
|
||||
SELECT 'ALTER TABLE ONLY ' || quote_ident(n.nspname) || '.' || quote_ident(c2.relname) || ' ADD CONSTRAINT ' || quote_ident(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;
|
||||
@@ -1186,7 +1186,7 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
-- 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 || ' '
|
||||
|| quote_ident(v_constraint_name) || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
END IF;
|
||||
@@ -1222,12 +1222,12 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
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 || ' '
|
||||
|| quote_ident(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
|
||||
SELECT 'ALTER TABLE ONLY ' || quote_ident(in_schema) || '.' || quote_ident(c.relname) || ' ADD CONSTRAINT ' || quote_ident(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
|
||||
@@ -1243,12 +1243,12 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
-- internal def
|
||||
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
|
||||
|| 'CONSTRAINT' || ' '
|
||||
|| v_constraint_name || ' '
|
||||
|| quote_ident(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
|
||||
SELECT 'ALTER TABLE ONLY ' || quote_ident(n.nspname) || '.' || quote_ident(c2.relname) || ' ADD CONSTRAINT ' || quote_ident(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';
|
||||
@@ -1257,7 +1257,7 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
-- 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 || ' '
|
||||
|| quote_ident(v_constraint_name) || ' '
|
||||
|| v_constraint_def
|
||||
|| ',' || E'\\n';
|
||||
END IF;
|
||||
@@ -1285,7 +1285,7 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
-- 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';
|
||||
v_table_ddl := v_table_ddl || ') INHERITS (' || quote_ident(v_parent_schema) || '.' || quote_ident(v_parent) || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
|
||||
END IF;
|
||||
|
||||
IF v_pgversion >= 100000 AND NOT bPartition and NOT bInheritance THEN
|
||||
@@ -1370,9 +1370,9 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
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';
|
||||
v_table_ddl := v_table_ddl || v_temp || ' TABLESPACE ' || quote_ident(v_indexrec.tablespace) || v_temp2 || ';' || E'\\n';
|
||||
ELSE
|
||||
v_table_ddl := v_table_ddl || v_indexrec.indexdef || ' TABLESPACE ' || v_indexrec.tablespace || ';' || E'\\n';
|
||||
v_table_ddl := v_table_ddl || v_indexrec.indexdef || ' TABLESPACE ' || quote_ident(v_indexrec.tablespace) || ';' || E'\\n';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
@@ -1383,8 +1383,8 @@ const SCOPED_PG_GET_TABLEDEF_SQL: SafeSqlFragment = safeSql`
|
||||
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
|
||||
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 || quote_ident(n.nspname) || '.' ||
|
||||
CASE WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN quote_ident(c.relname) || '.' || quote_ident(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
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { afterAll, expect, test } from 'vitest'
|
||||
|
||||
import { getTableDefinitionSql } from '../../../src'
|
||||
import { cleanupRoot, createTestDatabase } from '../../db/utils'
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupRoot()
|
||||
})
|
||||
|
||||
test('getTableDefinitionSql (scoped): escapes double quotes in schema, table, column and constraint names', async () => {
|
||||
const db = await createTestDatabase()
|
||||
try {
|
||||
await db.executeQuery(`
|
||||
create schema "pub""lic2";
|
||||
create table "pub""lic2"."te""st" (
|
||||
id bigint generated by default as identity,
|
||||
"we""ird" text not null,
|
||||
constraint "pk""test" primary key (id)
|
||||
);
|
||||
`)
|
||||
|
||||
const [{ id }] = await db.executeQuery<{ id: number }[]>(
|
||||
`select '"pub""lic2"."te""st"'::regclass::oid::int8 as id;`
|
||||
)
|
||||
|
||||
const sql = getTableDefinitionSql({ id, scoped: true })
|
||||
const [{ definition }] = await db.executeQuery<{ definition: string }[]>(sql)
|
||||
|
||||
expect(definition).toContain('"pub""lic2"')
|
||||
expect(definition).toContain('"te""st"')
|
||||
expect(definition).toContain('"we""ird"')
|
||||
expect(definition).toContain('"pk""test"')
|
||||
|
||||
// the generated DDL must itself be valid, executable SQL
|
||||
await db.executeQuery(`drop table "pub""lic2"."te""st";`)
|
||||
await db.executeQuery(definition)
|
||||
} finally {
|
||||
await db.cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('getTableDefinitionSql (scoped): quotes the parent schema for an inherited table', async () => {
|
||||
const db = await createTestDatabase()
|
||||
try {
|
||||
await db.executeQuery(`
|
||||
create schema "par""ent1";
|
||||
create table "par""ent1"."par""ent_tbl" (id bigint primary key);
|
||||
create schema "chi""ld1";
|
||||
create table "chi""ld1"."chi""ld_tbl" () inherits ("par""ent1"."par""ent_tbl");
|
||||
`)
|
||||
|
||||
const [{ id }] = await db.executeQuery<{ id: number }[]>(
|
||||
`select '"chi""ld1"."chi""ld_tbl"'::regclass::oid::int8 as id;`
|
||||
)
|
||||
|
||||
const sql = getTableDefinitionSql({ id, scoped: true })
|
||||
const [{ definition }] = await db.executeQuery<{ definition: string }[]>(sql)
|
||||
|
||||
expect(definition).toContain('INHERITS ("par""ent1"."par""ent_tbl")')
|
||||
|
||||
// the generated DDL must itself be valid, executable SQL
|
||||
await db.executeQuery(`drop table "chi""ld1"."chi""ld_tbl";`)
|
||||
await db.executeQuery(definition)
|
||||
} finally {
|
||||
await db.cleanup()
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user