1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-08 23:56:37 +02:00

150 lines
4.4 KiB
Go

package dbtable2crud
import (
"fmt"
"strings"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/pkg/errors"
)
type psqlColumn struct {
Table string
Column string
ColumnId int64
NotNull bool
DataTypeFull string
DataTypeName string
DataTypeLength *int
NumericPrecision *int
NumericScale *int
IsPrimaryKey bool
PrimaryKeyName *string
IsUniqueKey bool
UniqueKeyName *string
IsForeignKey bool
ForeignKeyName *string
ForeignKeyColumnId pq.Int64Array
ForeignKeyTable *string
ForeignKeyLocalColumnId pq.Int64Array
DefaultFull *string
DefaultValue *string
IsEnum bool
EnumTypeId *string
EnumValues []string
}
// descTable lists all the columns for a table.
func descTable(db *sqlx.DB, dbName, dbTable string) ([]psqlColumn, error) {
queryStr := fmt.Sprintf(`SELECT
c.relname as table,
f.attname as column,
f.attnum as columnId,
f.attnotnull as not_null,
pg_catalog.format_type(f.atttypid,f.atttypmod) AS data_type_full,
t.typname AS data_type_name,
CASE WHEN f.atttypmod >= 0 AND t.typname <> 'numeric'THEN (f.atttypmod - 4) --first 4 bytes are for storing actual length of data
END AS data_type_length,
CASE WHEN t.typname = 'numeric' THEN (((f.atttypmod - 4) >> 16) & 65535)
END AS numeric_precision,
CASE WHEN t.typname = 'numeric' THEN ((f.atttypmod - 4)& 65535 )
END AS numeric_scale,
CASE WHEN p.contype = 'p' THEN true ELSE false
END AS is_primary_key,
CASE WHEN p.contype = 'p' THEN p.conname
END AS primary_key_name,
CASE WHEN p.contype = 'u' THEN true ELSE false
END AS is_unique_key,
CASE WHEN p.contype = 'u' THEN p.conname
END AS unique_key_name,
CASE WHEN p.contype = 'f' THEN true ELSE false
END AS is_foreign_key,
CASE WHEN p.contype = 'f' THEN p.conname
END AS foreignkey_name,
CASE WHEN p.contype = 'f' THEN p.confkey
END AS foreign_key_columnid,
CASE WHEN p.contype = 'f' THEN g.relname
END AS foreign_key_table,
CASE WHEN p.contype = 'f' THEN p.conkey
END AS foreign_key_local_column_id,
CASE WHEN f.atthasdef = 't' THEN d.adsrc
END AS default_value,
CASE WHEN t.typtype = 'e' THEN true ELSE false
END AS is_enum,
CASE WHEN t.typtype = 'e' THEN t.oid
END AS enum_type_id
FROM pg_attribute f
JOIN pg_class c ON c.oid = f.attrelid
JOIN pg_type t ON t.oid = f.atttypid
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
WHERE c.relkind = 'r'::char
AND f.attisdropped = false
AND c.relname = '%s'
AND f.attnum > 0
ORDER BY f.attnum
;`, dbTable) // AND n.nspname = '%s'
rows, err := db.Query(queryStr)
if err != nil {
err = errors.Wrapf(err, "query - %s", queryStr)
return nil, err
}
// iterate over each row
var resp []psqlColumn
for rows.Next() {
var c psqlColumn
err = rows.Scan(&c.Table, &c.Column, &c.ColumnId, &c.NotNull, &c.DataTypeFull, &c.DataTypeName, &c.DataTypeLength, &c.NumericPrecision, &c.NumericScale, &c.IsPrimaryKey, &c.PrimaryKeyName, &c.IsUniqueKey, &c.UniqueKeyName, &c.IsForeignKey, &c.ForeignKeyName, &c.ForeignKeyColumnId, &c.ForeignKeyTable, &c.ForeignKeyLocalColumnId, &c.DefaultFull, &c.IsEnum, &c.EnumTypeId)
if err != nil {
err = errors.Wrapf(err, "query - %s", queryStr)
return nil, err
}
if c.DefaultFull != nil {
defaultValue := *c.DefaultFull
// "'active'::project_status_t"
defaultValue = strings.Split(defaultValue, "::")[0]
c.DefaultValue = &defaultValue
}
resp = append(resp, c)
}
for colIdx, dbCol := range resp {
if !dbCol.IsEnum {
continue
}
queryStr := fmt.Sprintf(`SELECT e.enumlabel
FROM pg_enum AS e
WHERE e.enumtypid = '%s'
ORDER BY e.enumsortorder`, *dbCol.EnumTypeId)
rows, err := db.Query(queryStr)
if err != nil {
err = errors.Wrapf(err, "query - %s", queryStr)
return nil, err
}
for rows.Next() {
var v string
err = rows.Scan(&v)
if err != nil {
err = errors.Wrapf(err, "query - %s", queryStr)
return nil, err
}
dbCol.EnumValues = append(dbCol.EnumValues, v)
}
resp[colIdx] = dbCol
}
return resp, nil
}