From 9daf71e2ed202251be18073704eee4194d8d3144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Sun, 28 May 2023 19:41:24 +0200 Subject: [PATCH] v0.0.125 --- sq/hasher.go | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++ sq/scanner.go | 77 +++++++++++++++++++- 2 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 sq/hasher.go diff --git a/sq/hasher.go b/sq/hasher.go new file mode 100644 index 0000000..8a5fc49 --- /dev/null +++ b/sq/hasher.go @@ -0,0 +1,195 @@ +package sq + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/jmoiron/sqlx" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "os" + "path/filepath" + "strings" +) + +func HashSqliteSchema(ctx context.Context, schemaStr string) (string, error) { + dbdir := os.TempDir() + dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3") + + err := os.MkdirAll(dbdir, os.ModePerm) + if err != nil { + return "", err + } + + url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s&_busy_timeout=%d", dbfile1, "DELETE", 1000, "true", 1000) + + xdb, err := sqlx.Open("sqlite3", url) + if err != nil { + return "", err + } + + db := NewDB(xdb) + + _, err = db.Exec(ctx, schemaStr, PP{}) + if err != nil { + return "", err + } + + ss, err := CreateSqliteDatabaseSchemaString(ctx, db) + if err != nil { + return "", err + } + + cs := sha256.Sum256([]byte(ss)) + + return hex.EncodeToString(cs[:]), nil +} + +func CreateSqliteDatabaseSchemaString(ctx context.Context, db DB) (string, error) { + + type colInfo struct { + Name string `db:"name"` + Type string `db:"type"` + NotNull string `db:"notnull"` + Default *string `db:"dflt_value"` + PrimaryKey *string `db:"pk"` + } + + type idxInfo struct { + Name string `json:"name" db:"name"` + Unique int `json:"unique" db:"unique"` + Origin string `json:"origin" db:"origin"` + Patial int `json:"partial" db:"partial"` + } + + type fkyInfo struct { + TableDest string `json:"table_dest" db:"table"` + From string `json:"from" db:"from"` + To string `json:"to" db:"to"` + OnUpdate string `json:"on_update" db:"on_update"` + OnDelete string `json:"on_delete" db:"on_delete"` + Match string `json:"match" db:"match"` + } + + type tabInfo struct { + Name string `json:"name" db:"name"` + Type string `json:"type" db:"type"` + NumCol int `json:"ncol" db:"ncol"` + Strict int `json:"strict" db:"strict"` + + ColumnInfo []colInfo `json:"-"` + IndexInfo []idxInfo `json:"-"` + FKeyInfo []fkyInfo `json:"-"` + } + + rowsTableList, err := db.Query(ctx, "PRAGMA table_list;", PP{}) + if err != nil { + return "", err + } + tableList, err := ScanAll[tabInfo](rowsTableList, SModeFast, Unsafe, true) + if err != nil { + return "", err + } + + langext.SortBy(tableList, func(v tabInfo) string { return v.Name }) + + result := make([]tabInfo, 0) + + for i, tab := range tableList { + + if strings.HasPrefix(tab.Name, "sqlite_") { + continue + } + + { + + rowsColumnList, err := db.Query(ctx, fmt.Sprintf("PRAGMA table_info(\"%s\");", tab.Name), PP{}) + if err != nil { + return "", err + } + + columnList, err := ScanAll[colInfo](rowsColumnList, SModeFast, Unsafe, true) + if err != nil { + return "", err + } + + langext.SortBy(columnList, func(v colInfo) string { return v.Name }) + + tableList[i].ColumnInfo = columnList + } + + { + rowsIdxList, err := db.Query(ctx, fmt.Sprintf("PRAGMA index_list(\"%s\");", tab.Name), PP{}) + if err != nil { + return "", err + } + idxList, err := ScanAll[idxInfo](rowsIdxList, SModeFast, Unsafe, true) + if err != nil { + return "", err + } + + langext.SortBy(idxList, func(v idxInfo) string { return v.Name }) + + tableList[i].IndexInfo = idxList + } + + { + rowsIdxList, err := db.Query(ctx, fmt.Sprintf("PRAGMA foreign_key_list(\"%s\");", tab.Name), PP{}) + if err != nil { + return "", err + } + fkyList, err := ScanAll[fkyInfo](rowsIdxList, SModeFast, Unsafe, true) + if err != nil { + return "", err + } + + langext.SortBy(fkyList, func(v fkyInfo) string { return v.From }) + + tableList[i].FKeyInfo = fkyList + } + + result = append(result, tableList[i]) + } + + strBuilderResult := "" + for _, vTab := range result { + jbinTable, err := json.Marshal(vTab) + if err != nil { + return "", err + } + + strBuilderResult += fmt.Sprintf("#TABLE: %s\n{\n", string(jbinTable)) + + for _, vCol := range vTab.ColumnInfo { + jbinColumn, err := json.Marshal(vCol) + if err != nil { + return "", err + } + + strBuilderResult += fmt.Sprintf(" COLUMN: %s\n", string(jbinColumn)) + } + + for _, vIdx := range vTab.IndexInfo { + jbinIndex, err := json.Marshal(vIdx) + if err != nil { + return "", err + } + + strBuilderResult += fmt.Sprintf(" INDEX: %s\n", string(jbinIndex)) + } + + for _, vFky := range vTab.FKeyInfo { + jbinFKey, err := json.Marshal(vFky) + if err != nil { + return "", err + } + + strBuilderResult += fmt.Sprintf(" FKEY: %s\n", string(jbinFKey)) + } + + strBuilderResult += "}\n\n" + } + + return strBuilderResult, nil +} diff --git a/sq/scanner.go b/sq/scanner.go index e77a64f..6544fdf 100644 --- a/sq/scanner.go +++ b/sq/scanner.go @@ -1,9 +1,13 @@ package sq import ( + "context" "database/sql" "errors" + "fmt" "github.com/jmoiron/sqlx" + "reflect" + "strings" ) type StructScanMode string @@ -16,10 +20,79 @@ const ( type StructScanSafety string const ( - Safe StructScanSafety = "SAFE" - Unsafe StructScanSafety = "UNSAFE" + Safe StructScanSafety = "SAFE" // return error for missing fields + Unsafe StructScanSafety = "UNSAFE" // ignore missing fields ) +func InsertSingle[TData any](ctx context.Context, q Queryable, tableName string, v TData) (sql.Result, error) { + + rval := reflect.ValueOf(v) + rtyp := rval.Type() + + columns := make([]string, 0) + params := make([]string, 0) + pp := PP{} + + for i := 0; i < rtyp.NumField(); i++ { + + rsfield := rtyp.Field(i) + rvfield := rval.Field(i) + + if !rsfield.IsExported() { + continue + } + + columnName := rsfield.Tag.Get("db") + if columnName == "" || columnName == "-" { + continue + } + + paramkey := fmt.Sprintf("_%s", columnName) + + columns = append(columns, "\""+columnName+"\"") + params = append(params, ":"+paramkey) + pp[paramkey] = rvfield.Interface() + + } + + sqlstr := fmt.Sprintf("INSERT"+" INTO \"%s\" (%s) VALUES (%s)", tableName, strings.Join(columns, ", "), strings.Join(params, ", ")) + + sqlr, err := q.Exec(ctx, sqlstr, pp) + if err != nil { + return nil, err + } + + return sqlr, nil +} + +func QuerySingle[TData any](ctx context.Context, q Queryable, sql string, pp PP, mode StructScanMode, sec StructScanSafety) (TData, error) { + rows, err := q.Query(ctx, sql, pp) + if err != nil { + return *new(TData), err + } + + data, err := ScanSingle[TData](rows, mode, sec, true) + if err != nil { + return *new(TData), err + } + + return data, nil +} + +func QueryAll[TData any](ctx context.Context, q Queryable, sql string, pp PP, mode StructScanMode, sec StructScanSafety) ([]TData, error) { + rows, err := q.Query(ctx, sql, pp) + if err != nil { + return nil, err + } + + data, err := ScanAll[TData](rows, mode, sec, true) + if err != nil { + return nil, err + } + + return data, nil +} + func ScanSingle[TData any](rows *sqlx.Rows, mode StructScanMode, sec StructScanSafety, close bool) (TData, error) { if rows.Next() { var strscan *StructScanner