|
1 | 1 | # GoSQLX Usage Guide |
2 | 2 |
|
| 3 | +**Version**: v1.5.1+ | **Last Updated**: November 2025 |
| 4 | + |
3 | 5 | ## Table of Contents |
4 | 6 | - [Getting Started](#getting-started) |
| 7 | +- [Simple API (Recommended)](#simple-api-recommended) |
5 | 8 | - [Basic Usage](#basic-usage) |
| 9 | +- [Advanced SQL Features (v1.4+)](#advanced-sql-features-v14) |
| 10 | +- [SQL Injection Detection](#sql-injection-detection) |
6 | 11 | - [Advanced Patterns](#advanced-patterns) |
7 | 12 | - [Real-World Examples](#real-world-examples) |
8 | 13 | - [SQL Dialect Support](#sql-dialect-support) |
@@ -31,6 +36,55 @@ import ( |
31 | 36 | ) |
32 | 37 | ``` |
33 | 38 |
|
| 39 | +## Simple API (Recommended) |
| 40 | + |
| 41 | +The simplest way to use GoSQLX is through the high-level API that handles all complexity for you: |
| 42 | + |
| 43 | +```go |
| 44 | +package main |
| 45 | + |
| 46 | +import ( |
| 47 | + "fmt" |
| 48 | + "log" |
| 49 | + |
| 50 | + "github.com/ajitpratap0/GoSQLX/pkg/gosqlx" |
| 51 | +) |
| 52 | + |
| 53 | +func main() { |
| 54 | + // Parse SQL in one line - that's it! |
| 55 | + ast, err := gosqlx.Parse("SELECT * FROM users WHERE active = true") |
| 56 | + if err != nil { |
| 57 | + log.Fatal(err) |
| 58 | + } |
| 59 | + |
| 60 | + fmt.Printf("Successfully parsed %d statement(s)\n", len(ast.Statements)) |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +### More Simple API Examples |
| 65 | + |
| 66 | +```go |
| 67 | +// Validate SQL without full parsing |
| 68 | +if err := gosqlx.Validate("SELECT * FROM users"); err != nil { |
| 69 | + fmt.Println("Invalid SQL:", err) |
| 70 | +} |
| 71 | + |
| 72 | +// Parse multiple queries efficiently |
| 73 | +queries := []string{ |
| 74 | + "SELECT * FROM users", |
| 75 | + "SELECT * FROM orders", |
| 76 | +} |
| 77 | +asts, err := gosqlx.ParseMultiple(queries) |
| 78 | + |
| 79 | +// Parse with timeout for long queries |
| 80 | +ast, err := gosqlx.ParseWithTimeout(sql, 5*time.Second) |
| 81 | + |
| 82 | +// Parse from byte slice (zero-copy) |
| 83 | +ast, err := gosqlx.ParseBytes([]byte("SELECT * FROM users")) |
| 84 | +``` |
| 85 | + |
| 86 | +> **Note:** The simple API has < 1% performance overhead compared to the low-level API. Use the simple API unless you need fine-grained control. |
| 87 | +
|
34 | 88 | ## Basic Usage |
35 | 89 |
|
36 | 90 | ### Simple Tokenization |
@@ -214,6 +268,166 @@ func HandleTokenizerError(sql string) { |
214 | 268 | } |
215 | 269 | ``` |
216 | 270 |
|
| 271 | +## Advanced SQL Features (v1.4+) |
| 272 | + |
| 273 | +### GROUPING SETS, ROLLUP, CUBE (SQL-99 T431) |
| 274 | + |
| 275 | +```go |
| 276 | +// GROUPING SETS - explicit grouping combinations |
| 277 | +sql := `SELECT region, product, SUM(sales) |
| 278 | + FROM orders |
| 279 | + GROUP BY GROUPING SETS ((region), (product), (region, product), ())` |
| 280 | +ast, err := gosqlx.Parse(sql) |
| 281 | + |
| 282 | +// ROLLUP - hierarchical subtotals |
| 283 | +sql := `SELECT year, quarter, month, SUM(revenue) |
| 284 | + FROM sales |
| 285 | + GROUP BY ROLLUP (year, quarter, month)` |
| 286 | +ast, err := gosqlx.Parse(sql) |
| 287 | + |
| 288 | +// CUBE - all possible combinations |
| 289 | +sql := `SELECT region, product, SUM(amount) |
| 290 | + FROM sales |
| 291 | + GROUP BY CUBE (region, product)` |
| 292 | +ast, err := gosqlx.Parse(sql) |
| 293 | +``` |
| 294 | + |
| 295 | +### MERGE Statements (SQL:2003 F312) |
| 296 | + |
| 297 | +```go |
| 298 | +sql := ` |
| 299 | + MERGE INTO target_table t |
| 300 | + USING source_table s ON t.id = s.id |
| 301 | + WHEN MATCHED THEN |
| 302 | + UPDATE SET t.name = s.name, t.value = s.value |
| 303 | + WHEN NOT MATCHED THEN |
| 304 | + INSERT (id, name, value) VALUES (s.id, s.name, s.value) |
| 305 | +` |
| 306 | +ast, err := gosqlx.Parse(sql) |
| 307 | +``` |
| 308 | + |
| 309 | +### Materialized Views |
| 310 | + |
| 311 | +```go |
| 312 | +// Create materialized view |
| 313 | +sql := `CREATE MATERIALIZED VIEW sales_summary AS |
| 314 | + SELECT region, SUM(amount) as total |
| 315 | + FROM sales GROUP BY region` |
| 316 | +ast, err := gosqlx.Parse(sql) |
| 317 | + |
| 318 | +// Refresh materialized view |
| 319 | +sql := `REFRESH MATERIALIZED VIEW CONCURRENTLY sales_summary` |
| 320 | +ast, err := gosqlx.Parse(sql) |
| 321 | + |
| 322 | +// Drop materialized view |
| 323 | +sql := `DROP MATERIALIZED VIEW IF EXISTS sales_summary` |
| 324 | +ast, err := gosqlx.Parse(sql) |
| 325 | +``` |
| 326 | + |
| 327 | +### Expression Operators (BETWEEN, IN, LIKE, IS NULL) |
| 328 | + |
| 329 | +```go |
| 330 | +// BETWEEN with expressions |
| 331 | +sql := `SELECT * FROM orders WHERE amount BETWEEN 100 AND 500` |
| 332 | + |
| 333 | +// IN with subquery |
| 334 | +sql := `SELECT * FROM users WHERE id IN (SELECT user_id FROM admins)` |
| 335 | + |
| 336 | +// LIKE with pattern matching |
| 337 | +sql := `SELECT * FROM products WHERE name LIKE '%widget%'` |
| 338 | + |
| 339 | +// IS NULL / IS NOT NULL |
| 340 | +sql := `SELECT * FROM users WHERE deleted_at IS NULL` |
| 341 | + |
| 342 | +// NULLS FIRST/LAST ordering (SQL-99 F851) |
| 343 | +sql := `SELECT * FROM users ORDER BY last_login DESC NULLS LAST` |
| 344 | +``` |
| 345 | + |
| 346 | +### Subqueries |
| 347 | + |
| 348 | +```go |
| 349 | +// Scalar subquery |
| 350 | +sql := `SELECT name, (SELECT MAX(salary) FROM employees) as max_sal FROM users` |
| 351 | + |
| 352 | +// EXISTS subquery |
| 353 | +sql := `SELECT * FROM orders o |
| 354 | + WHERE EXISTS (SELECT 1 FROM customers c WHERE c.id = o.customer_id)` |
| 355 | + |
| 356 | +// Correlated subquery |
| 357 | +sql := `SELECT * FROM employees e |
| 358 | + WHERE salary > (SELECT AVG(salary) FROM employees WHERE dept = e.dept)` |
| 359 | +``` |
| 360 | + |
| 361 | +## SQL Injection Detection |
| 362 | + |
| 363 | +GoSQLX includes a built-in security scanner (`pkg/sql/security`) for detecting SQL injection patterns: |
| 364 | + |
| 365 | +```go |
| 366 | +import ( |
| 367 | + "github.com/ajitpratap0/GoSQLX/pkg/gosqlx" |
| 368 | + "github.com/ajitpratap0/GoSQLX/pkg/sql/security" |
| 369 | +) |
| 370 | + |
| 371 | +func CheckForInjection(sql string) { |
| 372 | + // Parse the SQL first |
| 373 | + ast, err := gosqlx.Parse(sql) |
| 374 | + if err != nil { |
| 375 | + fmt.Println("Parse error:", err) |
| 376 | + return |
| 377 | + } |
| 378 | + |
| 379 | + // Create scanner and scan for injection patterns |
| 380 | + scanner := security.NewScanner() |
| 381 | + result := scanner.Scan(ast) |
| 382 | + |
| 383 | + // Check results |
| 384 | + if result.HasCritical() { |
| 385 | + fmt.Printf("CRITICAL: Found %d critical security issues!\n", result.CriticalCount) |
| 386 | + } |
| 387 | + if result.HasHigh() { |
| 388 | + fmt.Printf("HIGH: Found %d high-severity issues\n", result.HighCount) |
| 389 | + } |
| 390 | + |
| 391 | + // Print all findings |
| 392 | + for _, finding := range result.Findings { |
| 393 | + fmt.Printf("[%s] %s: %s\n", |
| 394 | + finding.Severity, |
| 395 | + finding.Pattern, |
| 396 | + finding.Description) |
| 397 | + } |
| 398 | +} |
| 399 | +``` |
| 400 | + |
| 401 | +### Detected Injection Patterns |
| 402 | + |
| 403 | +The security scanner detects: |
| 404 | +- **Tautology patterns**: `1=1`, `'a'='a'`, always-true conditions |
| 405 | +- **UNION-based injection**: Unauthorized UNION statements |
| 406 | +- **Time-based blind injection**: `SLEEP()`, `WAITFOR DELAY` |
| 407 | +- **Comment bypass**: `--`, `/**/` comment abuse |
| 408 | +- **Stacked queries**: Multiple statement injection |
| 409 | +- **Dangerous functions**: `xp_cmdshell`, `LOAD_FILE`, `INTO OUTFILE` |
| 410 | + |
| 411 | +```go |
| 412 | +// Example: Check user input for injection |
| 413 | +func ValidateUserQuery(userInput string) error { |
| 414 | + ast, err := gosqlx.Parse(userInput) |
| 415 | + if err != nil { |
| 416 | + return fmt.Errorf("invalid SQL syntax: %w", err) |
| 417 | + } |
| 418 | + |
| 419 | + scanner := security.NewScanner() |
| 420 | + result := scanner.Scan(ast) |
| 421 | + |
| 422 | + if result.HasCritical() || result.HasHigh() { |
| 423 | + return fmt.Errorf("potential SQL injection detected: %d issues found", |
| 424 | + result.CriticalCount + result.HighCount) |
| 425 | + } |
| 426 | + |
| 427 | + return nil |
| 428 | +} |
| 429 | +``` |
| 430 | + |
217 | 431 | ## Real-World Examples |
218 | 432 |
|
219 | 433 | ### SQL Validator |
|
0 commit comments