package main import ( "database/sql" "encoding/csv" "encoding/json" "fmt" "os" "strings" "inou/lib" _ "github.com/mattn/go-sqlite3" ) const dbPath = "/tank/inou/data/inou.db" func main() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "Usage: dbquery [OPTIONS] ") fmt.Fprintln(os.Stderr, " Runs SQL against inou.db, decrypts fields, outputs JSON (default).") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Options:") fmt.Fprintln(os.Stderr, " -csv Output as CSV") fmt.Fprintln(os.Stderr, " -table Output as formatted table") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Example: dbquery \"SELECT * FROM dossiers LIMIT 5\"") fmt.Fprintln(os.Stderr, " dbquery -csv \"SELECT * FROM dossiers\"") os.Exit(1) } // Parse options format := "json" args := os.Args[1:] if len(args) > 0 && (args[0] == "-csv" || args[0] == "--csv") { format = "csv" args = args[1:] } else if len(args) > 0 && (args[0] == "-table" || args[0] == "--table") { format = "table" args = args[1:] } if len(args) == 0 { fmt.Fprintln(os.Stderr, "Error: SQL query required") os.Exit(1) } query := strings.Join(args, " ") // Init crypto only (we open DB ourselves for raw queries) if err := lib.CryptoInit(lib.KeyPathDefault); err != nil { fmt.Fprintf(os.Stderr, "crypto init: %v\n", err) os.Exit(1) } db, err := sql.Open("sqlite3", dbPath) if err != nil { fmt.Fprintf(os.Stderr, "db open: %v\n", err) os.Exit(1) } defer db.Close() rows, err := db.Query(query) if err != nil { fmt.Fprintf(os.Stderr, "query: %v\n", err) os.Exit(1) } defer rows.Close() cols, err := rows.Columns() if err != nil { fmt.Fprintf(os.Stderr, "columns: %v\n", err) os.Exit(1) } var results []map[string]interface{} for rows.Next() { // Scan all as NullString scanDest := make([]interface{}, len(cols)) for i := range scanDest { scanDest[i] = new(sql.NullString) } if err := rows.Scan(scanDest...); err != nil { fmt.Fprintf(os.Stderr, "scan: %v\n", err) os.Exit(1) } row := make(map[string]interface{}) for i, col := range cols { ns := scanDest[i].(*sql.NullString) if !ns.Valid { row[col] = nil continue } val := ns.String // Recursive decrypt until value stops changing or max depth reached decrypted := val for j := 0; j < 10; j++ { next := lib.CryptoDecrypt(decrypted) if next == "" || next == decrypted { break } decrypted = next } // Use decrypted value if different from original if decrypted != val { // If decrypted looks like JSON, parse it if strings.HasPrefix(decrypted, "{") || strings.HasPrefix(decrypted, "[") { var parsed interface{} if json.Unmarshal([]byte(decrypted), &parsed) == nil { row[col] = parsed continue } } row[col] = decrypted } else { row[col] = val } } results = append(results, row) } if err := rows.Err(); err != nil { fmt.Fprintf(os.Stderr, "rows: %v\n", err) os.Exit(1) } // Output in requested format switch format { case "csv": outputCSV(cols, results) case "table": outputTable(cols, results) default: out, _ := json.MarshalIndent(results, "", " ") fmt.Println(string(out)) } } func outputCSV(cols []string, results []map[string]interface{}) { w := csv.NewWriter(os.Stdout) defer w.Flush() // Header w.Write(cols) // Rows for _, row := range results { record := make([]string, len(cols)) for i, col := range cols { val := row[col] if val == nil { record[i] = "" } else { record[i] = fmt.Sprintf("%v", val) } } w.Write(record) } } func outputTable(cols []string, results []map[string]interface{}) { if len(results) == 0 { fmt.Println("(no rows)") return } // Calculate column widths widths := make([]int, len(cols)) for i, col := range cols { widths[i] = len(col) } for _, row := range results { for i, col := range cols { val := fmt.Sprintf("%v", row[col]) if len(val) > widths[i] { widths[i] = len(val) } } } // Cap width at 50 chars for i := range widths { if widths[i] > 50 { widths[i] = 50 } } // Print header for i, col := range cols { fmt.Printf("%-*s", widths[i]+2, col) } fmt.Println() // Print separator for i := range cols { fmt.Print(strings.Repeat("-", widths[i]+2)) } fmt.Println() // Print rows for _, row := range results { for i, col := range cols { val := fmt.Sprintf("%v", row[col]) if len(val) > widths[i] { val = val[:widths[i]-3] + "..." } fmt.Printf("%-*s", widths[i]+2, val) } fmt.Println() } }