-1

My expectation is to generate DDL string from existing tables. Like most of the IDE's do with one click, but via Java. I am using PostgreSQL as database.

What I want to gather is simply this;

From mySchema.myTable

Column A Column B
Cell 1 Cell 2
Cell 3 Cell 4

Some magical code returns create table script as String

create table myTable (
   columnA ....
   columnB ....
)

and so on

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • 1
    table related to so many objects, pg_dump is the right tool. https://stackoverflow.com/questions/2593803/how-to-generate-the-create-table-sql-statement-for-an-existing-table-in-postgr?answertab=scoredesc#tab-top – jian Mar 29 '23 at 07:43
  • Yes, it is possible (if only because some of those IDEs are written in Java...), but your question is not specific enough for Stack Overflow. – Mark Rotteveel Mar 29 '23 at 08:23

1 Answers1

1

Generating the table definition is not that straight forward and this will not work 100% of the time.

You can use this function to generate/extract the definition of a table given an oid:

CREATE OR REPLACE FUNCTION public.tabledef(oid)
 RETURNS text
 LANGUAGE sql
 STRICT
AS $function$
/* snatched from https://github.com/filiprem/pg-tools */
WITH attrdef AS (
    SELECT
        n.nspname,
        c.relname,
        pg_catalog.array_to_string(c.reloptions || array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ') as relopts,
        c.relpersistence,
        a.attnum,
        a.attname,
        pg_catalog.format_type(a.atttypid, a.atttypmod) as atttype,
        (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d
            WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) as attdefault,
        a.attnotnull,
        (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t
            WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) as attcollation,
        a.attidentity,
        a.attgenerated
    FROM pg_catalog.pg_attribute a
    JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
    JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
    LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
    WHERE a.attrelid = $1
        AND a.attnum > 0
        AND NOT a.attisdropped
    ORDER BY a.attnum
),
coldef AS (
    SELECT
        attrdef.nspname,
        attrdef.relname,
        attrdef.relopts,
        attrdef.relpersistence,
        pg_catalog.format(
            '%I %s%s%s%s%s',
            attrdef.attname,
            attrdef.atttype,
            case when attrdef.attcollation is null then '' else pg_catalog.format(' COLLATE %I', attrdef.attcollation) end,
            case when attrdef.attnotnull then ' NOT NULL' else '' end,
            case when attrdef.attdefault is null then ''
                else case when attrdef.attgenerated = 's' then pg_catalog.format(' GENERATED ALWAYS AS (%s) STORED', attrdef.attdefault)
                    when attrdef.attgenerated <> '' then ' GENERATED AS NOT_IMPLEMENTED'
                    else pg_catalog.format(' DEFAULT %s', attrdef.attdefault)
                end
            end,
            case when attrdef.attidentity<>'' then pg_catalog.format(' GENERATED %s AS IDENTITY',
                    case attrdef.attidentity when 'd' then 'BY DEFAULT' when 'a' then 'ALWAYS' else 'NOT_IMPLEMENTED' end)
                else '' end
        ) as col_create_sql
    FROM attrdef
    ORDER BY attrdef.attnum
),
tabdef AS (
    SELECT
        coldef.nspname,
        coldef.relname,
        coldef.relopts,
        coldef.relpersistence,
        string_agg(coldef.col_create_sql, E',\n    ') as cols_create_sql
    FROM coldef
    GROUP BY
        coldef.nspname, coldef.relname, coldef.relopts, coldef.relpersistence
)
SELECT
    format(
        'CREATE%s TABLE %I.%I%s%s%s;',
        case tabdef.relpersistence when 't' then ' TEMP' when 'u' then ' UNLOGGED' else '' end,
        tabdef.nspname,
        tabdef.relname,
        coalesce(
            (SELECT format(E'\n    PARTITION OF %I.%I %s\n', pn.nspname, pc.relname,
                pg_get_expr(c.relpartbound, c.oid))
                FROM pg_class c JOIN pg_inherits i ON c.oid = i.inhrelid
                JOIN pg_class pc ON pc.oid = i.inhparent
                JOIN pg_namespace pn ON pn.oid = pc.relnamespace
                WHERE c.oid = $1),
            format(E' (\n    %s\n)', tabdef.cols_create_sql)
        ),
        case when tabdef.relopts <> '' then format(' WITH (%s)', tabdef.relopts) else '' end,
        coalesce(E'\nPARTITION BY '||pg_get_partkeydef($1), '')
    ) as table_create_sql
FROM tabdef
$function$
;


You can then call the function like this SELECT tabledef('<tablename>'::regclass::oid);

This version fetches the first oid matching the tablename. This could collide with a index or something similar if the name is not unique. In that case you need to check pg_class and find the oid that way. (SELECT * FROM pg_catalog.pg_class WHERE relname = '<tablename>').

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
mmoehrlein
  • 164
  • 9
  • 1
    That is not bad, but it is hard not to forget anything: for example, I don't see support for `CREATE TEMP TABLE ... ON COMMIT DROP`. In practice, running `pg_dump -s` is usually preferable. – Laurenz Albe Mar 29 '23 at 08:25
  • 1
    @LaurenzAlbe True. I edited the answer to state that this will not always work. I do aggree that pg_dump is the better option. This would just be an option of using pg_dump would not be possible for any reason or it needed to be realised through some kind of sql query. – mmoehrlein Mar 29 '23 at 08:35