1
0
mirror of https://github.com/ManyakRus/crud_generator.git synced 2024-11-28 20:41:15 +02:00
crud_generator/internal/postgres/postgres.go

645 lines
17 KiB
Go
Raw Normal View History

2023-09-28 16:45:44 +02:00
package postgres
import (
"context"
2023-10-27 10:43:15 +02:00
"database/sql"
2023-09-28 16:45:44 +02:00
"errors"
"fmt"
"github.com/ManyakRus/crud_generator/internal/config"
2023-11-08 16:37:35 +02:00
"github.com/ManyakRus/crud_generator/internal/create_files"
2023-09-28 16:45:44 +02:00
"github.com/ManyakRus/crud_generator/internal/types"
2023-10-27 10:43:15 +02:00
"github.com/ManyakRus/crud_generator/pkg/dbmeta"
2023-09-28 16:45:44 +02:00
"github.com/ManyakRus/starter/contextmain"
"github.com/ManyakRus/starter/log"
"github.com/ManyakRus/starter/postgres_gorm"
"strings"
"time"
)
type TableColumn struct {
TableName string `json:"table_name" gorm:"column:table_name;default:''"`
ColumnName string `json:"column_name" gorm:"column:column_name;default:''"`
ColumnType string `json:"type_name" gorm:"column:type_name;default:''"`
ColumnIsIdentity string `json:"is_identity" gorm:"column:is_identity;default:''"`
2023-10-26 09:33:18 +02:00
ColumnIsNullable string `json:"is_nullable" gorm:"column:is_nullable;default:''"`
2023-09-28 16:45:44 +02:00
ColumnDescription string `json:"description" gorm:"column:description;default:''"`
ColumnTableKey string `json:"table_key" gorm:"column:table_key;default:''"`
ColumnColumnKey string `json:"column_key" gorm:"column:column_key;default:''"`
2023-11-03 13:21:35 +02:00
TableComment string `json:"table_comment" gorm:"column:table_comment;default:''"`
2024-04-04 16:59:20 +02:00
IsPrimaryKey bool `json:"is_primary_key" gorm:"column:is_primary_key;default:false"`
2023-09-28 16:45:44 +02:00
}
2024-03-21 10:25:12 +02:00
type TableRowsStruct struct {
IDMinimum sql.NullString `json:"id_min" gorm:"column:id_min;default:0"`
RowsCount sql.NullInt64 `json:"rows_count" gorm:"column:rows_count;default:0"`
}
2023-09-28 16:45:44 +02:00
// FillMapTable - возвращает массив MassTable данными из БД
func FillMapTable() (map[string]*types.Table, error) {
2024-04-12 15:54:27 +02:00
MapTable := make(map[string]*types.Table, 0)
var err error
MapTable, err = FillMapTable1()
if err != nil {
log.Error("FillMapTable1() error: ", err)
return MapTable, err
}
2024-05-29 13:18:58 +02:00
err = FillIDMinimum_ManyPK(MapTable)
//err = FillIDMinimum(MapTable)
2024-04-12 15:54:27 +02:00
if err != nil {
log.Error("FillIDMinimum() error: ", err)
return MapTable, err
}
err = FillRowsCount(MapTable)
if err != nil {
log.Error("FillRowsCount() error: ", err)
return MapTable, err
}
return MapTable, err
}
// FillMapTable1 - возвращает массив MassTable данными из БД
func FillMapTable1() (map[string]*types.Table, error) {
2023-09-28 16:45:44 +02:00
var err error
//MassTable := make([]types.Table, 0)
MapTable := make(map[string]*types.Table, 0)
TextSQL := `
drop table if exists temp_keys;
CREATE TEMPORARY TABLE temp_keys (table_from text, column_from text, table_to text, column_to text);
------------------------------------------- Все внешние ключи ------------------------------
insert into temp_keys
SELECT
(select r.relname from pg_class r where r.oid = c.conrelid) as table_from,
UNNEST((select array_agg(attname) from pg_attribute where attrelid = c.conrelid and array[attnum] <@ c.conkey)) as column_from,
(select r.relname from pg_class r where r.oid = c.confrelid) as table_to,
a.attname as column_to
2023-11-03 13:21:35 +02:00
2023-09-28 16:45:44 +02:00
FROM
pg_constraint c
join
pg_attribute a
on
c.confrelid=a.attrelid and a.attnum = ANY(confkey)
WHERE 1=1
2023-11-03 13:21:35 +02:00
--and c.confrelid = (select oid from pg_class where relname = 'lawsuit_invoices')
2023-11-02 14:57:13 +02:00
--AND c.confrelid!=c.conrelid
2023-09-28 16:45:44 +02:00
;
2024-04-04 16:59:20 +02:00
------------------------------------------- Все primary keys ------------------------------
drop table if exists temp_primary_keys;
CREATE TEMPORARY TABLE temp_primary_keys (table_name text, column_name text);
insert into temp_primary_keys
select
ccu.table_name,
2024-05-02 15:20:22 +02:00
(ccu.column_name) as column_name
2024-04-04 16:59:20 +02:00
from pg_constraint pgc
join pg_namespace nsp on nsp.oid = pgc.connamespace
join pg_class cls on pgc.conrelid = cls.oid
left join information_schema.constraint_column_usage ccu
on pgc.conname = ccu.constraint_name
and nsp.nspname = ccu.constraint_schema
WHERE 1=1
2024-04-08 12:54:14 +02:00
and ccu.table_schema = 'public'
2024-04-04 16:59:20 +02:00
and contype = 'p'
2024-05-02 15:20:22 +02:00
--GROUP BY
-- ccu.table_name
--HAVING sum(1)=1
2024-04-04 16:59:20 +02:00
;
2023-09-28 16:45:44 +02:00
------------------------------------------- Все таблицы и колонки ------------------------------
SELECT
c.table_name,
c.column_name,
c.udt_name as type_name,
c.is_identity as is_identity,
2023-10-26 09:33:18 +02:00
c.is_nullable as is_nullable,
2023-09-28 16:45:44 +02:00
COALESCE(pgd.description, '') as description,
COALESCE(keys.table_to, '') as table_key,
2023-11-03 13:21:35 +02:00
COALESCE(keys.column_to, '') as column_key,
2024-04-04 16:59:20 +02:00
(SELECT obj_description(oid) FROM pg_class as r WHERE relkind = 'r' and r.oid = st.relid) as table_comment,
CASE
WHEN tpk.table_name is not null
THEN true
ELSE false END
as is_primary_key
2023-11-03 13:21:35 +02:00
2023-09-28 16:45:44 +02:00
FROM
pg_catalog.pg_statio_all_tables as st
right join
information_schema.columns c
on
2023-11-03 13:21:35 +02:00
c.table_schema = st.schemaname
2023-09-28 16:45:44 +02:00
and c.table_name = st.relname
2023-11-03 13:21:35 +02:00
left join
pg_catalog.pg_description pgd
on
pgd.objoid = st.relid
and pgd.objsubid = c.ordinal_position
2023-09-28 16:45:44 +02:00
LEFT JOIN --внешние ключи
temp_keys as keys
ON
keys.table_from = c.table_name
and keys.column_from = c.column_name
LEFT JOIN --вьюхи
INFORMATION_SCHEMA.views as v
ON
v.table_schema = 'public'
and v.table_name = c.table_name
2024-04-04 16:59:20 +02:00
LEFT JOIN
temp_primary_keys as tpk
ON
tpk.table_name = c.table_name
and tpk.column_name = c.column_name
2023-09-28 16:45:44 +02:00
where 1=1
and c.table_schema='public'
and v.table_name is null
--INCLUDE_TABLES
--EXCLUDE_TABLES
2023-11-03 13:21:35 +02:00
--and c.table_name = 'lawsuit_invoices'
2023-09-28 16:45:44 +02:00
order by
table_name,
is_identity desc,
column_name
2023-11-03 13:21:35 +02:00
2023-09-28 16:45:44 +02:00
`
SCHEMA := strings.Trim(postgres_gorm.Settings.DB_SCHEMA, " ")
if SCHEMA != "" {
TextSQL = strings.ReplaceAll(TextSQL, "public", SCHEMA)
}
if config.Settings.INCLUDE_TABLES != "" {
TextSQL = strings.ReplaceAll(TextSQL, "--INCLUDE_TABLES", "and c.table_name ~* '"+config.Settings.INCLUDE_TABLES+"'")
}
if config.Settings.EXCLUDE_TABLES != "" {
TextSQL = strings.ReplaceAll(TextSQL, "--EXCLUDE_TABLES", "and c.table_name !~* '"+config.Settings.EXCLUDE_TABLES+"'")
}
//соединение
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*60)
defer ctxCancelFunc()
db := postgres_gorm.GetConnection()
db.WithContext(ctx)
//запрос
//запустим все запросы отдельно
2024-04-04 16:59:20 +02:00
tx := postgres_gorm.RawMultipleSQL(db, TextSQL)
err = tx.Error
if err != nil {
log.Panic("RawMultipleSQL() error:", err)
2023-09-28 16:45:44 +02:00
}
2024-04-04 16:59:20 +02:00
//var tx *gorm.DB
//sqlSlice := strings.Split(TextSQL, ";")
//len1 := len(sqlSlice)
//for i, TextSQL1 := range sqlSlice {
// //batch.Queue(TextSQL1)
// if i == len1-1 {
// tx = db.Raw(TextSQL1)
// } else {
// tx = db.Exec(TextSQL1)
// //rows.Close()
// }
// err = tx.Error
// if err != nil {
// log.Panic("DB.Raw() error:", err)
// }
//}
2023-09-28 16:45:44 +02:00
//tx := db.Raw(TextSQL)
//err = tx.Error
//if err != nil {
// sError := fmt.Sprint("db.Raw() error: ", err)
// log.Panicln(sError)
// return MassTable, err
//}
//ответ в структуру
MassTableColumn := make([]TableColumn, 0)
tx = tx.Scan(&MassTableColumn)
err = tx.Error
if err != nil {
sError := fmt.Sprint("Get_error() error: ", err)
log.Panicln(sError)
return MapTable, err
}
//проверка 0 строк
if tx.RowsAffected == 0 {
sError := fmt.Sprint("db.Raw() RowsAffected =0 ")
log.Warn(sError)
err = errors.New(sError)
//log.Panicln(sError)
return MapTable, err
}
//заполним MapTable
2023-11-08 16:37:35 +02:00
MapColumns := make(map[string]*types.Column, 0)
2023-09-28 16:45:44 +02:00
OrderNumberColumn := 0
OrderNumberTable := 0
2024-05-02 15:20:22 +02:00
PrimaryKeyColumnsCount := 0
2023-09-28 16:45:44 +02:00
TableName0 := ""
Table1 := CreateTable()
for _, v := range MassTableColumn {
if v.TableName != TableName0 {
OrderNumberColumn = 0
Table1.MapColumns = MapColumns
2024-05-02 15:20:22 +02:00
Table1.PrimaryKeyColumnsCount = PrimaryKeyColumnsCount
2023-11-08 16:37:35 +02:00
MapColumns = make(map[string]*types.Column, 0)
2023-09-28 16:45:44 +02:00
if TableName0 != "" {
//MassTable = append(MassTable, Table1)
MapTable[TableName0] = Table1
OrderNumberTable++
}
2023-11-16 17:07:59 +02:00
2024-05-02 16:56:29 +02:00
//новая таблица
2023-11-16 17:07:59 +02:00
//найдём имя модели golang
TableName := v.TableName
ModelName := create_files.FindSingularName(TableName)
ModelName = create_files.FormatName(ModelName)
//
2024-04-09 17:04:05 +02:00
TableComment := v.TableComment
TableComment = strings.ReplaceAll(TableComment, "\n", "")
TableComment = strings.ReplaceAll(TableComment, "\r", "")
2024-05-02 16:56:29 +02:00
2023-09-28 16:45:44 +02:00
Table1 = CreateTable()
2023-11-16 17:07:59 +02:00
Table1.Name = TableName
2023-09-28 16:45:44 +02:00
Table1.OrderNumber = OrderNumberTable
2024-04-09 17:04:05 +02:00
Table1.Comment = TableComment
2023-11-16 17:07:59 +02:00
Table1.NameGo = ModelName
2024-05-02 15:20:22 +02:00
PrimaryKeyColumnsCount = 0
2023-09-28 16:45:44 +02:00
}
Column1 := types.Column{}
Column1.Name = v.ColumnName
Column1.Type = v.ColumnType
2023-11-08 16:37:35 +02:00
FillNameGo(&Column1)
2023-10-27 10:43:15 +02:00
//Type_go
2023-11-08 16:37:35 +02:00
FillTypeGo(&Column1)
//SQLMapping1, ok := dbmeta.GetMappings()[Column1.Type]
//if ok == false {
// log.Panic("GetMappings() ", Column1.Type, " error: not found")
//}
//Type_go := SQLMapping1.GoType
//Column1.TypeGo = Type_go
2023-10-27 10:43:15 +02:00
//
2023-09-28 16:45:44 +02:00
if v.ColumnIsIdentity == "YES" {
2023-10-26 09:33:18 +02:00
Column1.IsIdentity = true
}
if v.ColumnIsNullable == "YES" {
Column1.IsNullable = true
2023-09-28 16:45:44 +02:00
}
Column1.Description = v.ColumnDescription
Column1.OrderNumber = OrderNumberColumn
Column1.TableKey = v.ColumnTableKey
Column1.ColumnKey = v.ColumnColumnKey
2024-04-04 16:59:20 +02:00
Column1.IsPrimaryKey = v.IsPrimaryKey
2024-05-02 15:20:22 +02:00
if v.IsPrimaryKey == true {
PrimaryKeyColumnsCount++
}
2023-09-28 16:45:44 +02:00
2023-11-08 16:37:35 +02:00
MapColumns[v.ColumnName] = &Column1
2023-09-28 16:45:44 +02:00
//Table1.Columns = append(Table1.Columns, Column1)
OrderNumberColumn++
TableName0 = v.TableName
}
2023-10-27 10:43:15 +02:00
2024-05-02 16:56:29 +02:00
//последнюю таблицу заполним тут
2023-09-28 16:45:44 +02:00
if Table1.Name != "" {
Table1.MapColumns = MapColumns
2024-05-02 16:56:29 +02:00
Table1.PrimaryKeyColumnsCount = PrimaryKeyColumnsCount
2023-09-28 16:45:44 +02:00
MapTable[TableName0] = Table1
}
2023-10-27 10:43:15 +02:00
//FillTypeGo(MapTable)
2023-09-28 16:45:44 +02:00
return MapTable, err
}
func CreateTable() *types.Table {
Otvet := &types.Table{}
2023-11-08 16:37:35 +02:00
Otvet.MapColumns = make(map[string]*types.Column, 0)
2023-09-28 16:45:44 +02:00
return Otvet
}
2023-10-27 10:43:15 +02:00
2024-03-21 10:25:12 +02:00
// FillIDMinimum - находим минимальный ID, для тестов с этим ID
2024-04-12 15:54:27 +02:00
func FillIDMinimum(MapTable map[string]*types.Table) error {
2023-10-27 10:43:15 +02:00
var err error
//соединение
db := postgres_gorm.GetConnection()
ctxMain := contextmain.GetContext()
2024-04-04 16:59:20 +02:00
Schema := strings.Trim(postgres_gorm.Settings.DB_SCHEMA, " ")
2023-10-27 10:43:15 +02:00
for TableName, Table1 := range MapTable {
//текст запроса
2024-04-04 16:59:20 +02:00
NameID, TypeGo := FindNameType_from_PrimaryKey(Table1)
2023-10-27 10:43:15 +02:00
if NameID == "" {
continue
}
2024-04-04 16:59:20 +02:00
TextSQL := ""
Is_UUID_Type := create_files.Is_UUID_Type(TypeGo)
if Is_UUID_Type == false {
DefaultValueSQL := create_files.FindTextDefaultValueSQL(TypeGo)
2024-04-04 17:14:31 +02:00
TextSQL = `SELECT
Min("` + NameID + `") as id_minimum
FROM
"` + Schema + `"."` + TableName + `"
WHERE
"` + NameID + `" <> ` + DefaultValueSQL
2024-04-04 16:59:20 +02:00
} else {
2024-04-12 15:54:27 +02:00
TextSQL = `SELECT "` + NameID + `" as id_minimum
FROM "` + Schema + `"."` + TableName + `"
2024-04-05 15:53:35 +02:00
WHERE "` + NameID + `" is not null
2024-04-04 17:14:31 +02:00
ORDER BY ` + NameID + `
LIMIT 1`
2024-04-04 16:59:20 +02:00
}
2023-10-27 10:43:15 +02:00
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*60)
defer ctxCancelFunc()
db.WithContext(ctx)
//запрос
tx := db.Raw(TextSQL)
err = tx.Error
if err != nil {
log.Panic("Wrong SQL query: ", TextSQL, " error: ", err)
}
var IDMinimum sql.NullString
tx = tx.Scan(&IDMinimum)
err = tx.Error
if err != nil {
log.Panic("Wrong SQL Scan(): ", TextSQL, " error: ", err)
}
//
2024-05-29 13:18:58 +02:00
ColumnPK := create_files.FindPrimaryKeyColumn(Table1)
ColumnPK.IDMinimum = IDMinimum.String
}
return err
}
// FillIDMinimum_ManyPK - находим минимальный ID, для тестов с этим ID, для многих Primary Key
func FillIDMinimum_ManyPK(MapTable map[string]*types.Table) error {
var err error
//соединение
db := postgres_gorm.GetConnection()
ctxMain := contextmain.GetContext()
Schema := strings.Trim(postgres_gorm.Settings.DB_SCHEMA, " ")
for TableName, Table1 := range MapTable {
ColumnsPK := create_files.FindPrimaryKeyColumns(Table1)
Is_UUID_Type := false
for _, Column1 := range ColumnsPK {
Is_UUID_Type1 := create_files.Is_UUID_Type(Column1.TypeGo)
Is_UUID_Type = Is_UUID_Type || Is_UUID_Type1
}
//текст запроса
TextSQL := ""
if Is_UUID_Type == false {
TextSQL = `SELECT
`
Comma := ""
for _, Column1 := range ColumnsPK {
TextSQL += Comma + `Min("` + Column1.Name + `") as "` + Column1.Name + `"`
Comma = ","
}
TextSQL = TextSQL + `
FROM
"` + Schema + `"."` + TableName + `"
WHERE 1=1`
for _, Column1 := range ColumnsPK {
DefaultValueSQL := create_files.FindTextDefaultValueSQL(Column1.TypeGo)
TextSQL += `and ` + Column1.Name + ` <> ` + DefaultValueSQL
}
} else {
TextSQL = `SELECT
`
Comma := ""
for _, Column1 := range ColumnsPK {
TextSQL += Comma + `"` + Column1.Name + `" as "` + Column1.Name + `"`
Comma = ","
}
TextSQL = TextSQL + `
FROM
"` + Schema + `"."` + TableName + `"
WHERE 1=1`
for _, Column1 := range ColumnsPK {
TextSQL += `and ` + Column1.Name + ` is not null `
}
TextSQL = TextSQL + `
ORDER BY
`
Comma = ""
for _, Column1 := range ColumnsPK {
TextSQL += Comma + `"` + Column1.Name + `"`
Comma = ","
}
//TextSQL = `SELECT "` + NameID + `" as id_minimum
// FROM "` + Schema + `"."` + TableName + `"
// WHERE "` + NameID + `" is not null
// ORDER BY ` + NameID + `
// LIMIT 1`
}
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*60)
defer ctxCancelFunc()
db.WithContext(ctx)
//запрос с разным количеством колонок
tx := db.Raw(TextSQL)
err = tx.Error
if err != nil {
log.Panic("Raw() Wrong SQL query: ", TextSQL, " error: ", err)
}
rows, err := tx.Rows()
if err != nil {
log.Panic("Rows() Wrong SQL query: ", TextSQL, " error: ", err)
}
has_next := rows.Next()
if has_next == false {
log.Panic("Next() Wrong SQL query: ", TextSQL, " error: ", err)
}
ColumnsGorm, err := rows.Columns()
MapIDMinimum := make(map[string]string)
values := make([]interface{}, len(ColumnsGorm))
for i := range values {
values[i] = new(interface{})
}
if err := rows.Scan(values...); err != nil {
return err
}
for i, colName := range ColumnsGorm {
value1 := ""
value1 = fmt.Sprint(*values[i].(*interface{}))
if value1 == "<nil>" {
value1 = ""
}
MapIDMinimum[colName] = value1
}
//
for _, Column1 := range ColumnsPK {
value := MapIDMinimum[Column1.Name]
Column1.IDMinimum = value
}
2023-10-27 10:43:15 +02:00
}
2024-04-12 15:54:27 +02:00
return err
2023-10-27 10:43:15 +02:00
}
2024-03-21 10:25:12 +02:00
// FillRowsCount - находим количество строк в таблице, для кэша
2024-04-12 15:54:27 +02:00
func FillRowsCount(MapTable map[string]*types.Table) error {
2024-03-21 10:25:12 +02:00
var err error
//соединение
db := postgres_gorm.GetConnection()
ctxMain := contextmain.GetContext()
2024-04-04 16:59:20 +02:00
Schema := strings.Trim(postgres_gorm.Settings.DB_SCHEMA, " ")
2024-03-21 10:25:12 +02:00
for TableName, Table1 := range MapTable {
//текст запроса
//только Postgres SQL
TextSQL := `
SELECT
reltuples::bigint AS rows_count
FROM
pg_class
WHERE
2024-04-04 16:59:20 +02:00
oid = '` + Schema + `."` + TableName + `"'::regclass
2024-03-21 10:25:12 +02:00
`
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*60)
defer ctxCancelFunc()
db.WithContext(ctx)
//запрос
tx := db.Raw(TextSQL)
err = tx.Error
if err != nil {
log.Panic("Wrong SQL query: ", TextSQL, " error: ", err)
}
var RowsCount sql.NullInt64
//TableRows := TableRowsStruct{}
tx = tx.Scan(&RowsCount)
err = tx.Error
if err != nil {
log.Panic("Wrong SQL Scan(): ", TextSQL, " error: ", err)
}
Table1.RowsCount = RowsCount.Int64
}
2024-04-12 15:54:27 +02:00
return err
2024-03-21 10:25:12 +02:00
}
2024-04-04 16:59:20 +02:00
// FindNameType_from_PrimaryKey - возвращает наименование и тип БД для колонки PrimaryKey (ID)
func FindNameType_from_PrimaryKey(Table1 *types.Table) (string, string) {
2023-10-27 10:43:15 +02:00
Otvet := ""
Type := ""
for ColumnName, Column1 := range Table1.MapColumns {
2024-04-04 16:59:20 +02:00
if Column1.IsPrimaryKey == true {
2023-10-27 10:43:15 +02:00
return ColumnName, Column1.TypeGo
}
}
return Otvet, Type
}
//// FillTypeGo - заполняет тип golang из типа postgres
//func FillTypeGo(MapTable map[string]*types.Table) {
//
// for _, Table1 := range MapTable {
// for _, Column1 := range Table1.MapColumns {
//
// SQLMapping1, ok := dbmeta.GetMappings()[Column1.Type]
// if ok == false {
// log.Panic("GetMappings() ", Column1.Type, " error: not found")
// }
// Type_go := SQLMapping1.GoType
// Column1.TypeGo = Type_go
// }
// }
//
//}
2023-11-08 16:37:35 +02:00
// FillNameGo - заполняет NameGo во все колонки
func FillNameGo(Column1 *types.Column) error {
var err error
ColumnName := Column1.Name
ColumnNameGo := create_files.FormatName(ColumnName)
Column1.NameGo = ColumnNameGo
if ColumnNameGo == "" {
err = errors.New("Column: " + ColumnName + " Type: " + Column1.Type + " NameGo= \"\"")
}
return err
}
// FillTypeGo - заполняет тип golang из типа postgres
func FillTypeGo(Column1 *types.Column) error {
var err error
SQLMapping1, ok := dbmeta.GetMappings()[Column1.Type]
if ok == false {
log.Panic("GetMappings() ", Column1.Type, " error: not found")
}
ColumnName := Column1.Name
Type_go := SQLMapping1.GoType
Column1.TypeGo = Type_go
if Type_go == "" {
err = errors.New("Column: " + ColumnName + " Type: " + Column1.Type + " TypeGo= \"\"")
}
return err
}