package sq import ( "context" "database/sql" "errors" "fmt" "github.com/jmoiron/sqlx" "reflect" "strings" ) type StructScanMode string const ( SModeFast StructScanMode = "FAST" // Use default sq.Scan, does not work with joined/resolved types and/or custom value converter SModeExtended StructScanMode = "EXTENDED" // Fully featured perhaps (?) a tiny bit slower - default ) type StructScanSafety string const ( 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) val, err := convertValueToDB(q, rvfield.Interface()) if err != nil { return nil, err } pp[paramkey] = val } 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](ctx, q, 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](ctx, q, rows, mode, sec, true) if err != nil { return nil, err } return data, nil } func ScanSingle[TData any](ctx context.Context, q Queryable, rows *sqlx.Rows, mode StructScanMode, sec StructScanSafety, close bool) (TData, error) { if rows.Next() { var strscan *StructScanner if sec == Safe { strscan = NewStructScanner(rows, false) var data TData err := strscan.Start(&data) if err != nil { return *new(TData), err } } else if sec == Unsafe { strscan = NewStructScanner(rows, true) var data TData err := strscan.Start(&data) if err != nil { return *new(TData), err } } else { return *new(TData), errors.New("unknown value for ") } var data TData if mode == SModeFast { err := strscan.StructScanBase(&data) if err != nil { return *new(TData), err } } else if mode == SModeExtended { err := strscan.StructScanExt(q, &data) if err != nil { return *new(TData), err } } else { return *new(TData), errors.New("unknown value for ") } if rows.Next() { if close { _ = rows.Close() } return *new(TData), errors.New("sql returned more than one row") } if close { err := rows.Close() if err != nil { return *new(TData), err } } if err := rows.Err(); err != nil { return *new(TData), err } if err := ctx.Err(); err != nil { return *new(TData), err } return data, nil } else { if close { _ = rows.Close() } return *new(TData), sql.ErrNoRows } } func ScanAll[TData any](ctx context.Context, q Queryable, rows *sqlx.Rows, mode StructScanMode, sec StructScanSafety, close bool) ([]TData, error) { var strscan *StructScanner if sec == Safe { strscan = NewStructScanner(rows, false) var data TData err := strscan.Start(&data) if err != nil { return nil, err } } else if sec == Unsafe { strscan = NewStructScanner(rows, true) var data TData err := strscan.Start(&data) if err != nil { return nil, err } } else { return nil, errors.New("unknown value for ") } res := make([]TData, 0) for rows.Next() { if err := ctx.Err(); err != nil { return nil, err } if mode == SModeFast { var data TData err := strscan.StructScanBase(&data) if err != nil { return nil, err } res = append(res, data) } else if mode == SModeExtended { var data TData err := strscan.StructScanExt(q, &data) if err != nil { return nil, err } res = append(res, data) } else { return nil, errors.New("unknown value for ") } } if close { err := strscan.rows.Close() if err != nil { return nil, err } } if err := rows.Err(); err != nil { return nil, err } return res, nil }