Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:58:35 +08:00
commit 322d28c7eb
14 changed files with 7534 additions and 0 deletions

View File

@@ -0,0 +1,725 @@
// Package examples demonstrates advanced query patterns with cool-mysql
package examples
import (
"encoding/json"
"fmt"
"html/template"
"log"
"strings"
"time"
mysql "github.com/StirlingMarketingGroup/cool-mysql"
)
// AdvancedQueryExamples demonstrates advanced query patterns
func AdvancedQueryExamples() {
db, err := setupDatabase()
if err != nil {
log.Fatalf("Failed to setup database: %v", err)
}
// Template queries
fmt.Println("=== TEMPLATE QUERY EXAMPLES ===")
templateExamples(db)
// Channel streaming
fmt.Println("\n=== CHANNEL STREAMING EXAMPLES ===")
channelExamples(db)
// Function receivers
fmt.Println("\n=== FUNCTION RECEIVER EXAMPLES ===")
functionReceiverExamples(db)
// JSON handling
fmt.Println("\n=== JSON HANDLING EXAMPLES ===")
jsonExamples(db)
// Raw SQL
fmt.Println("\n=== RAW SQL EXAMPLES ===")
rawSQLExamples(db)
// Complex queries
fmt.Println("\n=== COMPLEX QUERY EXAMPLES ===")
complexQueryExamples(db)
}
// templateExamples demonstrates Go template syntax in queries
func templateExamples(db *mysql.Database) {
// Example 1: Conditional WHERE clause
fmt.Println("1. Conditional WHERE clause")
type SearchParams struct {
MinAge int
Status string
Name string
}
// Search with all parameters
params := SearchParams{
MinAge: 25,
Status: "active",
Name: "Alice",
}
query := "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`" +
" WHERE 1=1" +
" {{ if .MinAge }}AND `age` >= @@MinAge{{ end }}" +
" {{ if .Status }}AND `status` = @@Status{{ end }}" +
" {{ if .Name }}AND `name` LIKE CONCAT('%', @@Name, '%'){{ end }}"
var users []User
err := db.Select(&users, query, 0, params)
if err != nil {
log.Printf("Template query failed: %v", err)
} else {
fmt.Printf("✓ Found %d users with filters\n", len(users))
}
// Example 2: Dynamic ORDER BY (with validation)
fmt.Println("\n2. Dynamic ORDER BY with whitelisting")
type SortParams struct {
SortBy string
SortOrder string
}
// Whitelist allowed columns - identifiers can't be marshaled
allowedColumns := map[string]bool{
"created_at": true,
"name": true,
"age": true,
}
sortParams := SortParams{
SortBy: "created_at",
SortOrder: "DESC",
}
// Validate before using in query
if !allowedColumns[sortParams.SortBy] {
log.Printf("Invalid sort column: %s", sortParams.SortBy)
return
}
sortQuery := "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`" +
" WHERE `active` = 1" +
" {{ if .SortBy }}" +
" ORDER BY {{ .SortBy }} {{ .SortOrder }}" +
" {{ end }}"
err = db.Select(&users, sortQuery, 0, sortParams)
if err != nil {
log.Printf("Sort query failed: %v", err)
} else {
fmt.Printf("✓ Users sorted by %s %s\n", sortParams.SortBy, sortParams.SortOrder)
}
// Example 3: Conditional JOINs
fmt.Println("\n3. Conditional JOINs")
type JoinParams struct {
IncludeOrders bool
IncludeAddress bool
IncludeMetadata bool
}
joinParams := JoinParams{
IncludeOrders: true,
IncludeAddress: false,
}
joinQuery := "SELECT `users`.`id`, `users`.`name`, `users`.`email`, `users`.`age`, `users`.`active`, `users`.`created_at`, `users`.`updated_at`" +
" {{ if .IncludeOrders }}, COUNT(`orders`.`id`) as `order_count`{{ end }}" +
" {{ if .IncludeAddress }}, `addresses`.`city`{{ end }}" +
" FROM `users`" +
" {{ if .IncludeOrders }}" +
" LEFT JOIN `orders` ON `users`.`id` = `orders`.`user_id`" +
" {{ end }}" +
" {{ if .IncludeAddress }}" +
" LEFT JOIN `addresses` ON `users`.`id` = `addresses`.`user_id`" +
" {{ end }}" +
" GROUP BY `users`.`id`"
err = db.Select(&users, joinQuery, 0, joinParams)
if err != nil {
log.Printf("Join query failed: %v", err)
} else {
fmt.Println("✓ Query with conditional joins executed")
}
// Example 4: Custom template functions
fmt.Println("\n4. Custom template functions")
// Add custom functions
db.AddTemplateFuncs(template.FuncMap{
"upper": strings.ToUpper,
"lower": strings.ToLower,
"quote": func(s string) string { return fmt.Sprintf("'%s'", s) },
})
type CaseParams struct {
SearchTerm string
CaseSensitive bool
UseWildcard bool
}
caseParams := CaseParams{
SearchTerm: "alice",
CaseSensitive: false,
UseWildcard: true,
}
// IMPORTANT: Template values must be marshaled with | marshal
caseQuery := "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`" +
" WHERE {{ if not .CaseSensitive }}UPPER(`name`){{ else }}`name`{{ end }} LIKE CONCAT('%', {{ .SearchTerm | marshal }}, '%')"
err = db.Select(&users, caseQuery, 0, caseParams)
if err != nil {
log.Printf("Custom function query failed: %v", err)
} else {
fmt.Println("✓ Query with custom template functions executed")
}
// Example 5: Complex conditional logic
fmt.Println("\n5. Complex conditional logic")
type FilterParams struct {
AgeRange []int
Statuses []string
DateFrom time.Time
DateTo time.Time
ActiveOnly bool
}
filterParams := FilterParams{
AgeRange: []int{25, 40},
ActiveOnly: true,
DateFrom: time.Now().Add(-30 * 24 * time.Hour),
}
filterQuery := "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`" +
" WHERE 1=1" +
" {{ if .AgeRange }}" +
" AND `age` BETWEEN @@AgeMin AND @@AgeMax" +
" {{ end }}" +
" {{ if .ActiveOnly }}" +
" AND `active` = 1" +
" {{ end }}" +
" {{ if not .DateFrom.IsZero }}" +
" AND `created_at` >= @@DateFrom" +
" {{ end }}" +
" {{ if not .DateTo.IsZero }}" +
" AND `created_at` <= @@DateTo" +
" {{ end }}"
queryParams := mysql.Params{
"AgeMin": filterParams.AgeRange[0],
"AgeMax": filterParams.AgeRange[1],
"DateFrom": filterParams.DateFrom,
}
err = db.Select(&users, filterQuery, 0, queryParams, filterParams)
if err != nil {
log.Printf("Complex filter query failed: %v", err)
} else {
fmt.Printf("✓ Found %d users with complex filters\n", len(users))
}
}
// channelExamples demonstrates streaming with channels
func channelExamples(db *mysql.Database) {
// Example 1: Stream SELECT results
fmt.Println("1. Stream SELECT results to channel")
userCh := make(chan User, 10) // Buffered channel
go func() {
defer close(userCh)
err := db.Select(userCh, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `active` = 1", 0)
if err != nil {
log.Printf("Channel select failed: %v", err)
}
}()
count := 0
for user := range userCh {
fmt.Printf(" Received: %s (%s)\n", user.Name, user.Email)
count++
if count >= 5 {
fmt.Println(" (showing first 5 results)")
// Drain remaining
for range userCh {
}
break
}
}
// Example 2: Stream INSERT from channel
fmt.Println("\n2. Stream INSERT from channel")
insertCh := make(chan User, 10)
go func() {
defer close(insertCh)
for i := 0; i < 100; i++ {
insertCh <- User{
Name: fmt.Sprintf("StreamUser%d", i),
Email: fmt.Sprintf("stream%d@example.com", i),
Age: 20 + (i % 50),
Active: i%2 == 0,
}
}
}()
err := db.Insert("users", insertCh)
if err != nil {
log.Printf("Channel insert failed: %v", err)
} else {
fmt.Println("✓ Streamed 100 users for insertion")
}
// Example 3: Transform while streaming
fmt.Println("\n3. Transform data while streaming")
type EnrichedUser struct {
User
Category string
Priority int
}
rawUserCh := make(chan User, 10)
enrichedCh := make(chan EnrichedUser, 10)
// Producer: fetch users
go func() {
defer close(rawUserCh)
db.Select(rawUserCh, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` LIMIT @@limit", 0, 50)
}()
// Transformer: enrich data
go func() {
defer close(enrichedCh)
for user := range rawUserCh {
enriched := EnrichedUser{
User: user,
}
// Add category based on age
if user.Age < 25 {
enriched.Category = "Young"
enriched.Priority = 1
} else if user.Age < 40 {
enriched.Category = "Middle"
enriched.Priority = 2
} else {
enriched.Category = "Senior"
enriched.Priority = 3
}
enrichedCh <- enriched
}
}()
// Consumer: process enriched data
processed := 0
for enriched := range enrichedCh {
processed++
_ = enriched // Process enriched user
}
fmt.Printf("✓ Processed %d enriched users\n", processed)
}
// functionReceiverExamples demonstrates function receivers
func functionReceiverExamples(db *mysql.Database) {
// Example 1: Process each row with function
fmt.Println("1. Process rows with function")
count := 0
err := db.Select(func(u User) {
count++
if count <= 3 {
fmt.Printf(" Processing: %s (Age: %d)\n", u.Name, u.Age)
}
}, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `active` = 1", 0)
if err != nil {
log.Printf("Function receiver failed: %v", err)
} else {
fmt.Printf("✓ Processed %d users\n", count)
}
// Example 2: Aggregate data with function
fmt.Println("\n2. Aggregate data with function")
var totalAge int
var userCount int
err = db.Select(func(u User) {
totalAge += u.Age
userCount++
}, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`", 0)
if err != nil {
log.Printf("Aggregation failed: %v", err)
} else if userCount > 0 {
avgAge := float64(totalAge) / float64(userCount)
fmt.Printf("✓ Average age: %.2f (%d users)\n", avgAge, userCount)
}
// Example 3: Conditional processing
fmt.Println("\n3. Conditional processing with function")
type Stats struct {
YoungCount int
MiddleCount int
SeniorCount int
}
stats := Stats{}
err = db.Select(func(u User) {
switch {
case u.Age < 25:
stats.YoungCount++
case u.Age < 40:
stats.MiddleCount++
default:
stats.SeniorCount++
}
}, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`", 0)
if err != nil {
log.Printf("Stats failed: %v", err)
} else {
fmt.Printf("✓ Age distribution: Young=%d, Middle=%d, Senior=%d\n",
stats.YoungCount, stats.MiddleCount, stats.SeniorCount)
}
// Example 4: Early termination pattern
fmt.Println("\n4. Early termination with function")
found := false
targetEmail := "alice@example.com"
err = db.Select(func(u User) {
if u.Email == targetEmail {
found = true
fmt.Printf("✓ Found user: %s\n", u.Name)
// Note: Can't actually stop iteration early
// This is a limitation of function receivers
}
}, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`", 0)
if err != nil {
log.Printf("Search failed: %v", err)
} else if !found {
fmt.Println("✗ User not found")
}
}
// jsonExamples demonstrates JSON handling
func jsonExamples(db *mysql.Database) {
// Example 1: Store JSON in struct field
fmt.Println("1. Store JSON column in struct")
type UserWithMeta struct {
ID int `mysql:"id"`
Name string `mysql:"name"`
Email string `mysql:"email"`
Metadata json.RawMessage `mysql:"metadata"` // JSON column
}
userMeta := UserWithMeta{
Name: "JSONUser",
Email: "json@example.com",
Metadata: json.RawMessage(`{
"theme": "dark",
"language": "en",
"notifications": true
}`),
}
err := db.Insert("users", userMeta)
if err != nil {
log.Printf("JSON insert failed: %v", err)
} else {
fmt.Println("✓ User with JSON metadata inserted")
}
// Example 2: Select JSON as RawMessage
fmt.Println("\n2. Select JSON column")
var usersWithMeta []UserWithMeta
err = db.Select(&usersWithMeta,
"SELECT `id`, `name`, `email`, metadata FROM `users` WHERE metadata IS NOT NULL LIMIT @@limit",
0,
5)
if err != nil {
log.Printf("JSON select failed: %v", err)
} else {
fmt.Printf("✓ Retrieved %d users with metadata\n", len(usersWithMeta))
for _, u := range usersWithMeta {
fmt.Printf(" %s: %s\n", u.Name, string(u.Metadata))
}
}
// Example 3: SelectJSON for JSON result
fmt.Println("\n3. SelectJSON for JSON object")
var jsonResult json.RawMessage
err = db.SelectJSON(&jsonResult,
"SELECT JSON_OBJECT("+
" 'id', `id`,"+
" 'name', `name`,"+
" 'email', `email`,"+
" 'age', `age`"+
" ) FROM `users` WHERE `id` = @@id",
0,
1)
if err != nil {
log.Printf("SelectJSON failed: %v", err)
} else {
fmt.Printf("✓ JSON result: %s\n", string(jsonResult))
}
// Example 4: SelectJSON for JSON array
fmt.Println("\n4. SelectJSON for JSON array")
var jsonArray json.RawMessage
err = db.SelectJSON(&jsonArray,
"SELECT JSON_ARRAYAGG("+
" JSON_OBJECT("+
" 'name', `name`,"+
" 'email', `email`"+
" )"+
" ) FROM `users` WHERE `active` = 1 LIMIT @@limit",
0,
5)
if err != nil {
log.Printf("SelectJSON array failed: %v", err)
} else {
fmt.Printf("✓ JSON array result: %s\n", string(jsonArray))
}
}
// rawSQLExamples demonstrates using mysql.Raw for literal SQL
func rawSQLExamples(db *mysql.Database) {
// Example 1: Raw SQL in WHERE clause
fmt.Println("1. Raw SQL for complex condition")
var users []User
err := db.Select(&users,
"SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE @@condition",
0,
mysql.Raw("created_at > NOW() - INTERVAL 7 DAY"))
if err != nil {
log.Printf("Raw SQL query failed: %v", err)
} else {
fmt.Printf("✓ Found %d users from last 7 days\n", len(users))
}
// Example 2: Raw SQL for CASE statement
fmt.Println("\n2. Raw SQL for CASE statement")
type UserWithLabel struct {
Name string `mysql:"name"`
Label string `mysql:"label"`
}
caseSQL := mysql.Raw(`
CASE
WHEN age < 25 THEN 'Young'
WHEN age < 40 THEN 'Middle'
ELSE 'Senior'
END
`)
var labeled []UserWithLabel
err = db.Select(&labeled,
"SELECT name, @@ageCase as `label` FROM `users`",
0,
caseSQL)
if err != nil {
log.Printf("CASE query failed: %v", err)
} else {
fmt.Printf("✓ Retrieved %d users with age labels\n", len(labeled))
for i, u := range labeled {
if i < 3 {
fmt.Printf(" %s: %s\n", u.Name, u.Label)
}
}
}
// Example 3: Raw SQL for subquery
fmt.Println("\n3. Raw SQL for subquery")
subquery := mysql.Raw("(SELECT AVG(age) FROM `users` WHERE `active` = 1)")
err = db.Select(&users,
"SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE age > @@avgAge",
0,
subquery)
if err != nil {
log.Printf("Subquery failed: %v", err)
} else {
fmt.Printf("✓ Found %d users above average age\n", len(users))
}
// Example 4: WARNING - Never use Raw with user input!
fmt.Println("\n4. WARNING: Raw SQL security example")
// DANGEROUS - SQL injection risk!
// userInput := "'; DROP TABLE users; --"
// db.Select(&users, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `name` = @@name", 0,
// mysql.Params{"name": mysql.Raw(userInput)})
// SAFE - use regular parameter
safeInput := "Alice'; DROP TABLE users; --"
err = db.Select(&users, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `name` = @@name", 0, safeInput) // Properly escaped
if err != nil {
log.Printf("Safe query failed: %v", err)
} else {
fmt.Println("✓ User input safely escaped (no SQL injection)")
}
}
// complexQueryExamples demonstrates complex query patterns
func complexQueryExamples(db *mysql.Database) {
// Example 1: Subquery with named parameters
fmt.Println("1. Subquery with parameters")
type UserWithOrderCount struct {
User
OrderCount int `mysql:"order_count"`
}
var usersWithOrders []UserWithOrderCount
err := db.Select(&usersWithOrders,
"SELECT `users`.`id`, `users`.`name`, `users`.`email`, `users`.`age`, `users`.`active`, `users`.`created_at`, `users`.`updated_at`,"+
" (SELECT COUNT(*) FROM `orders` WHERE `orders`.`user_id` = `users`.`id`) as `order_count`"+
" FROM `users`"+
" WHERE `users`.`created_at` > @@since"+
" AND `users`.`active` = @@active",
5*time.Minute,
mysql.Params{
"since": time.Now().Add(-30 * 24 * time.Hour),
"active": true,
})
if err != nil {
log.Printf("Subquery failed: %v", err)
} else {
fmt.Printf("✓ Retrieved %d active users with order counts\n", len(usersWithOrders))
}
// Example 2: JOIN with aggregation
fmt.Println("\n2. JOIN with aggregation")
query := "SELECT" +
" `users`.`id`," +
" `users`.`name`," +
" `users`.`email`," +
" COUNT(`orders`.`id`) as `order_count`," +
" SUM(`orders`.`total`) as `total_spent`" +
" FROM `users`" +
" LEFT JOIN `orders` ON `users`.`id` = `orders`.`user_id`" +
" WHERE `users`.`active` = @@active" +
" GROUP BY `users`.`id`, `users`.`name`, `users`.`email`" +
" HAVING COUNT(`orders`.`id`) > @@minOrders" +
" ORDER BY total_spent DESC" +
" LIMIT @@limit"
type UserStats struct {
ID int `mysql:"id"`
Name string `mysql:"name"`
Email string `mysql:"email"`
OrderCount int `mysql:"order_count"`
TotalSpent float64 `mysql:"total_spent"`
}
var stats []UserStats
err = db.Select(&stats, query, 10*time.Minute,
mysql.Params{
"active": true,
"minOrders": 5,
"limit": 10,
})
if err != nil {
log.Printf("Aggregation query failed: %v", err)
} else {
fmt.Printf("✓ Top %d spenders retrieved\n", len(stats))
}
// Example 3: Window function
fmt.Println("\n3. Window function query")
windowQuery := "SELECT" +
" `id`," +
" `name`," +
" `age`," +
" RANK() OVER (ORDER BY `age` DESC) as `age_rank`," +
" AVG(`age`) OVER () as `avg_age`" +
" FROM `users`" +
" WHERE `active` = @@active" +
" LIMIT @@limit"
type UserWithRank struct {
ID int `mysql:"id"`
Name string `mysql:"name"`
Age int `mysql:"age"`
AgeRank int `mysql:"age_rank"`
AvgAge float64 `mysql:"avg_age"`
}
var ranked []UserWithRank
err = db.Select(&ranked, windowQuery, 5*time.Minute,
mysql.Params{
"active": true,
"limit": 20,
})
if err != nil {
log.Printf("Window function query failed: %v", err)
} else {
fmt.Printf("✓ Retrieved %d users with age ranking\n", len(ranked))
}
// Example 4: CTE (Common Table Expression)
fmt.Println("\n4. CTE query")
cteQuery := "WITH recent_users AS (" +
" SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`" +
" WHERE `created_at` > @@since" +
" )," +
" active_recent AS (" +
" SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM recent_users" +
" WHERE `active` = @@active" +
" )" +
" SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM active_recent" +
" ORDER BY `created_at` DESC" +
" LIMIT @@limit"
var cteUsers []User
err = db.Select(&cteUsers, cteQuery, 5*time.Minute,
mysql.Params{
"since": time.Now().Add(-7 * 24 * time.Hour),
"active": true,
"limit": 10,
})
if err != nil {
log.Printf("CTE query failed: %v", err)
} else {
fmt.Printf("✓ Retrieved %d recent active users via CTE\n", len(cteUsers))
}
}