Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
9daf71e2ed
|
|||
fe278f7772
|
|||
8ebda6fb3a
|
|||
b0d3ce8c1c
|
|||
021465e524
|
|||
cf9c73aa4a
|
@@ -433,3 +433,10 @@ func ArrConcat[T any](arr ...[]T) []T {
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ArrCopy does a shallow copy of the 'in' array
|
||||
func ArrCopy[T any](in []T) []T {
|
||||
out := make([]T, len(in))
|
||||
copy(out, in)
|
||||
return out
|
||||
}
|
||||
|
@@ -31,16 +31,16 @@ func CompareIntArr(arr1 []int, arr2 []int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func CompareArr[T OrderedConstraint](arr1 []T, arr2 []T) bool {
|
||||
func CompareArr[T OrderedConstraint](arr1 []T, arr2 []T) int {
|
||||
|
||||
for i := 0; i < len(arr1) || i < len(arr2); i++ {
|
||||
|
||||
if i < len(arr1) && i < len(arr2) {
|
||||
|
||||
if arr1[i] < arr2[i] {
|
||||
return true
|
||||
return -1
|
||||
} else if arr1[i] > arr2[i] {
|
||||
return false
|
||||
return +2
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
@@ -49,17 +49,17 @@ func CompareArr[T OrderedConstraint](arr1 []T, arr2 []T) bool {
|
||||
|
||||
if i < len(arr1) {
|
||||
|
||||
return true
|
||||
return +1
|
||||
|
||||
} else { // if i < len(arr2)
|
||||
|
||||
return false
|
||||
return -1
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
return 0
|
||||
}
|
||||
|
||||
func CompareString(a, b string) int {
|
||||
|
@@ -1,5 +1,10 @@
|
||||
package langext
|
||||
|
||||
type MapEntry[T comparable, V any] struct {
|
||||
Key T
|
||||
Value V
|
||||
}
|
||||
|
||||
func MapKeyArr[T comparable, V any](v map[T]V) []T {
|
||||
result := make([]T, 0, len(v))
|
||||
for k := range v {
|
||||
@@ -8,6 +13,14 @@ func MapKeyArr[T comparable, V any](v map[T]V) []T {
|
||||
return result
|
||||
}
|
||||
|
||||
func MapValueArr[T comparable, V any](v map[T]V) []V {
|
||||
result := make([]V, 0, len(v))
|
||||
for _, mv := range v {
|
||||
result = append(result, mv)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ArrToMap[T comparable, V any](a []V, keyfunc func(V) T) map[T]V {
|
||||
result := make(map[T]V, len(a))
|
||||
for _, v := range a {
|
||||
@@ -16,6 +29,17 @@ func ArrToMap[T comparable, V any](a []V, keyfunc func(V) T) map[T]V {
|
||||
return result
|
||||
}
|
||||
|
||||
func MapToArr[T comparable, V any](v map[T]V) []MapEntry[T, V] {
|
||||
result := make([]MapEntry[T, V], 0, len(v))
|
||||
for mk, mv := range v {
|
||||
result = append(result, MapEntry[T, V]{
|
||||
Key: mk,
|
||||
Value: mv,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func CopyMap[K comparable, V any](a map[K]V) map[K]V {
|
||||
result := make(map[K]V, len(a))
|
||||
for k, v := range a {
|
||||
@@ -23,3 +47,11 @@ func CopyMap[K comparable, V any](a map[K]V) map[K]V {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ForceMap[K comparable, V any](v map[K]V) map[K]V {
|
||||
if v == nil {
|
||||
return make(map[K]V, 0)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
@@ -41,6 +41,14 @@ func NewHexUUID() (string, error) {
|
||||
return string(dst), nil
|
||||
}
|
||||
|
||||
func MustHexUUID() string {
|
||||
v, err := NewHexUUID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func NewUpperHexUUID() (string, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err != nil {
|
||||
@@ -64,6 +72,14 @@ func NewUpperHexUUID() (string, error) {
|
||||
return strings.ToUpper(string(dst)), nil
|
||||
}
|
||||
|
||||
func MustUpperHexUUID() string {
|
||||
v, err := NewUpperHexUUID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func NewRawHexUUID() (string, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err != nil {
|
||||
@@ -83,6 +99,14 @@ func NewRawHexUUID() (string, error) {
|
||||
return strings.ToUpper(string(dst)), nil
|
||||
}
|
||||
|
||||
func MustRawHexUUID() string {
|
||||
v, err := NewRawHexUUID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func NewBracesUUID() (string, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err != nil {
|
||||
@@ -108,6 +132,14 @@ func NewBracesUUID() (string, error) {
|
||||
return strings.ToUpper(string(dst)), nil
|
||||
}
|
||||
|
||||
func MustBracesUUID() string {
|
||||
v, err := NewBracesUUID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func NewParensUUID() (string, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err != nil {
|
||||
@@ -132,3 +164,11 @@ func NewParensUUID() (string, error) {
|
||||
|
||||
return strings.ToUpper(string(dst)), nil
|
||||
}
|
||||
|
||||
func MustParensUUID() string {
|
||||
v, err := NewParensUUID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
195
sq/hasher.go
Normal file
195
sq/hasher.go
Normal file
@@ -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
|
||||
}
|
@@ -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
|
||||
|
Reference in New Issue
Block a user