944 lines
33 KiB
Plaintext
944 lines
33 KiB
Plaintext
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"time"
|
|
|
|
"api-service/internal/config"
|
|
"api-service/internal/database"
|
|
"api-service/internal/utils/query"
|
|
"api-service/internal/validation"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
)
|
|
|
|
// This file provides comprehensive examples of using the query builder library
|
|
// for performing various database operations including CRUD, transactions, joins, etc.
|
|
// Each example function demonstrates how to build queries, print them, and execute them.
|
|
// =============================================================================
|
|
// DEFINISI MODEL (CONTOH)
|
|
// =============================================================================
|
|
|
|
// User adalah contoh struct untuk tabel 'users'.
|
|
type User struct {
|
|
ID int `db:"id" bson:"_id,omitempty"`
|
|
Name string `db:"name" bson:"name"`
|
|
Email string `db:"email" bson:"email"`
|
|
Status string `db:"status" bson:"status"`
|
|
CreatedAt time.Time `db:"created_at" bson:"created_at"`
|
|
}
|
|
|
|
// Post adalah contoh struct untuk tabel 'posts'.
|
|
type Post struct {
|
|
ID int `db:"id" bson:"_id,omitempty"`
|
|
UserID int `db:"user_id" bson:"user_id"`
|
|
Title string `db:"title" bson:"title"`
|
|
Content string `db:"content" bson:"content"`
|
|
CreatedAt time.Time `db:"created_at" bson:"created_at"`
|
|
}
|
|
|
|
// Employee adalah contoh struct untuk tabel 'employees' dengan kolom JSON.
|
|
type Employee struct {
|
|
ID int `db:"id" bson:"_id,omitempty"`
|
|
Name string `db:"name" bson:"name"`
|
|
Department string `db:"department" bson:"department"`
|
|
Salary float64 `db:"salary" bson:"salary"`
|
|
Metadata map[string]interface{} `db:"metadata" bson:"metadata"` // Kolom JSON/JSONB
|
|
}
|
|
|
|
// =============================================================================
|
|
// FUNGSI UTAMA
|
|
// =============================================================================
|
|
|
|
func main() {
|
|
cfg := setupConfig()
|
|
dbService := database.New(cfg)
|
|
|
|
fmt.Println("============================================================")
|
|
fmt.Println(" CONTOH 1: QUERY DASAR (SELECT, INSERT, UPDATE, DELETE)")
|
|
fmt.Println("============================================================")
|
|
basicCRUDExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 2: TRANSAKSI SQL (POSTGRESQL)")
|
|
fmt.Println("============================================================")
|
|
sqlTransactionExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 3: TRANSAKSI MONGODB")
|
|
fmt.Println("============================================================")
|
|
mongoTransactionExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 4: QUERY DENGAN FILTER DAN PAGINASI")
|
|
fmt.Println("============================================================")
|
|
filterAndPaginationExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 5: QUERY DENGAN JOIN")
|
|
fmt.Println("============================================================")
|
|
joinExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 6: QUERY DENGAN CTE (COMMON TABLE EXPRESSION)")
|
|
fmt.Println("============================================================")
|
|
cteExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 7: QUERY DENGAN WINDOW FUNCTION")
|
|
fmt.Println("============================================================")
|
|
windowFunctionExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 8: VALIDASI DATA DINAMIS")
|
|
fmt.Println("============================================================")
|
|
validationExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 9: OPERASI JSON")
|
|
fmt.Println("============================================================")
|
|
jsonQueryExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 10: QUERY MONGODB (CRUD & AGGREGATION)")
|
|
fmt.Println("============================================================")
|
|
mongodbExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 11: PENGGUNAAN READ REPLICA")
|
|
fmt.Println("============================================================")
|
|
readReplicaExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 12: HEALTH CHECK DATABASE")
|
|
fmt.Println("============================================================")
|
|
healthCheckExample(dbService)
|
|
|
|
fmt.Println("\n============================================================")
|
|
fmt.Println(" CONTOH 13: PARSING QUERY DARI URL")
|
|
fmt.Println("============================================================")
|
|
urlQueryParsingExample(dbService)
|
|
}
|
|
|
|
func setupConfig() *config.Config {
|
|
return &config.Config{
|
|
Databases: map[string]config.DatabaseConfig{
|
|
"main": {
|
|
Type: "postgres",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Username: "user",
|
|
Password: "password",
|
|
Database: "company_db",
|
|
SSLMode: "disable",
|
|
MaxOpenConns: 25,
|
|
MaxIdleConns: 5,
|
|
ConnMaxLifetime: time.Hour,
|
|
},
|
|
},
|
|
"mongodb": config.DatabaseConfig{
|
|
Type: "mongodb",
|
|
Host: "localhost",
|
|
Port: 27017,
|
|
Database: "company_db",
|
|
Username: "user",
|
|
Password: "password",
|
|
},
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 1: QUERY DASAR (CRUD)
|
|
// =============================================================================
|
|
|
|
// basicCRUDExample demonstrates basic Create, Read, Update, Delete operations using the query builder.
|
|
// It shows how to build SQL queries, print them, and execute them while displaying results.
|
|
// Expected output: Prints INSERT SQL and result (new ID), SELECT SQL and user data, UPDATE SQL and affected rows, DELETE SQL and affected rows.
|
|
// Example raw queries:
|
|
// INSERT: INSERT INTO users (name, email, status) VALUES ($1, $2, $3) RETURNING id
|
|
// SELECT: SELECT * FROM users WHERE id = $1
|
|
// UPDATE: UPDATE users SET status = $1 WHERE id = $2
|
|
// DELETE: DELETE FROM users WHERE id = $1
|
|
func basicCRUDExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
db, err := dbService.GetSQLXDB("main")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan koneksi DB: %v", err)
|
|
return
|
|
}
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
|
|
// --- INSERT ---
|
|
fmt.Println("\n--- Operasi INSERT ---")
|
|
insertData := query.InsertData{
|
|
Columns: []string{"name", "email", "status"},
|
|
Values: []interface{}{"Alice", "alice@example.com", "active"},
|
|
}
|
|
sql, args, err := qb.BuildInsertQuery("users", insertData, "id")
|
|
if err != nil {
|
|
log.Printf("Error building INSERT: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated INSERT SQL: %s\nArgs: %v\n", sql, args)
|
|
result, err := qb.ExecuteInsert(ctx, db, "users", insertData, "id")
|
|
if err != nil {
|
|
log.Printf("Error INSERT: %v", err)
|
|
return
|
|
}
|
|
newID, _ := result.LastInsertId()
|
|
fmt.Printf("-> INSERT: Berhasil menambah user dengan ID: %d\n", newID)
|
|
|
|
// --- SELECT (Single Row) ---
|
|
fmt.Println("\n--- Operasi SELECT ---")
|
|
var user User
|
|
selectQuery := query.DynamicQuery{
|
|
Fields: []query.SelectField{{Expression: "*"}},
|
|
From: "users",
|
|
Filters: []query.FilterGroup{{
|
|
Filters: []query.DynamicFilter{{Column: "id", Operator: query.OpEqual, Value: newID}},
|
|
}},
|
|
}
|
|
sql, args, err = qb.BuildQuery(selectQuery)
|
|
if err != nil {
|
|
log.Printf("Error building SELECT: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated SELECT SQL: %s\nArgs: %v\n", sql, args)
|
|
err = qb.ExecuteQueryRow(ctx, db, selectQuery, &user)
|
|
if err != nil {
|
|
log.Printf("Error SELECT single row: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> SELECT (Single Row): Berhasil mengambil user: %+v\n", user)
|
|
|
|
// --- UPDATE ---
|
|
fmt.Println("\n--- Operasi UPDATE ---")
|
|
updateData := query.UpdateData{
|
|
Columns: []string{"status"},
|
|
Values: []interface{}{"inactive"},
|
|
}
|
|
updateFilter := []query.FilterGroup{{
|
|
Filters: []query.DynamicFilter{{Column: "id", Operator: query.OpEqual, Value: newID}},
|
|
}}
|
|
sql, args, err = qb.BuildUpdateQuery("users", updateData, updateFilter)
|
|
if err != nil {
|
|
log.Printf("Error building UPDATE: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated UPDATE SQL: %s\nArgs: %v\n", sql, args)
|
|
_, err = qb.ExecuteUpdate(ctx, db, "users", updateData, updateFilter)
|
|
if err != nil {
|
|
log.Printf("Error UPDATE: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> UPDATE: Berhasil memperbarui status user dengan ID: %d\n", newID)
|
|
|
|
// --- DELETE ---
|
|
fmt.Println("\n--- Operasi DELETE ---")
|
|
deleteFilter := []query.FilterGroup{{
|
|
Filters: []query.DynamicFilter{{Column: "id", Operator: query.OpEqual, Value: newID}},
|
|
}}
|
|
sql, args, err = qb.BuildDeleteQuery("users", deleteFilter)
|
|
if err != nil {
|
|
log.Printf("Error building DELETE: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated DELETE SQL: %s\nArgs: %v\n", sql, args)
|
|
_, err = qb.ExecuteDelete(ctx, db, "users", deleteFilter)
|
|
if err != nil {
|
|
log.Printf("Error DELETE: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> DELETE: Berhasil menghapus user dengan ID: %d\n", newID)
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 2: TRANSAKSI SQL (POSTGRESQL)
|
|
// =============================================================================
|
|
|
|
// sqlTransactionExample demonstrates how to perform atomic transactions involving updates
|
|
// across multiple tables using the Query Builder. It builds and prints SQL queries before execution.
|
|
// Expected output: Prints UPDATE SQL for salaries and employees, transaction commit/rollback status, and validation results.
|
|
// Example raw queries:
|
|
// UPDATE salaries: UPDATE salaries SET salary = $1 WHERE employee_id = $2
|
|
// UPDATE employees: UPDATE employees SET last_name = $1 WHERE employee_id = $2
|
|
func sqlTransactionExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
employeeID := 123
|
|
newSalary := 75000
|
|
newLastName := "Doe"
|
|
|
|
db, err := dbService.GetSQLXDB("main")
|
|
if err != nil {
|
|
log.Fatalf("Gagal mendapatkan koneksi database SQL: %v", err)
|
|
}
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
|
|
tx, err := db.BeginTxx(ctx, nil)
|
|
if err != nil {
|
|
log.Fatalf("Gagal memulai transaksi SQL: %v", err)
|
|
}
|
|
|
|
defer func() {
|
|
if p := recover(); p != nil {
|
|
fmt.Println("Terjadi panic, melakukan rollback transaksi...")
|
|
_ = tx.Rollback()
|
|
panic(p)
|
|
} else if err != nil {
|
|
fmt.Printf("Transaksi dibatalkan (ROLLBACK) karena error: %v\n", err)
|
|
_ = tx.Rollback()
|
|
} else {
|
|
fmt.Println("Tidak ada error, melakukan COMMIT transaksi...")
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
log.Printf("Gagal melakukan COMMIT transaksi: %v", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
fmt.Printf("Memulai transaksi untuk employee_id: %d\n", employeeID)
|
|
|
|
// --- Operasi 1: Update gaji di tabel 'salaries' ---
|
|
fmt.Println("\n--- Operasi 1: UPDATE salaries ---")
|
|
salariesUpdateData := query.UpdateData{
|
|
Columns: []string{"salary"},
|
|
Values: []interface{}{newSalary},
|
|
}
|
|
salariesFilter := []query.FilterGroup{
|
|
{
|
|
Filters: []query.DynamicFilter{
|
|
{Column: "employee_id", Operator: query.OpEqual, Value: employeeID},
|
|
},
|
|
},
|
|
}
|
|
sql, args, err := qb.BuildUpdateQuery("salaries", salariesUpdateData, salariesFilter)
|
|
if err != nil {
|
|
log.Printf("Error building UPDATE salaries: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated UPDATE salaries SQL: %s\nArgs: %v\n", sql, args)
|
|
salariesResult, err := qb.ExecuteUpdate(ctx, tx, "salaries", salariesUpdateData, salariesFilter)
|
|
if err != nil {
|
|
return
|
|
}
|
|
salariesRowsAffected, _ := salariesResult.RowsAffected()
|
|
fmt.Printf("-> UPDATE salaries: %d baris terpengaruh.\n", salariesRowsAffected)
|
|
|
|
// --- Operasi 2: Update informasi di tabel 'employees' ---
|
|
fmt.Println("\n--- Operasi 2: UPDATE employees ---")
|
|
employeesUpdateData := query.UpdateData{
|
|
Columns: []string{"last_name"},
|
|
Values: []interface{}{newLastName},
|
|
}
|
|
employeesFilter := []query.FilterGroup{
|
|
{
|
|
Filters: []query.DynamicFilter{
|
|
{Column: "employee_id", Operator: query.OpEqual, Value: employeeID},
|
|
},
|
|
},
|
|
}
|
|
sql, args, err = qb.BuildUpdateQuery("employees", employeesUpdateData, employeesFilter)
|
|
if err != nil {
|
|
log.Printf("Error building UPDATE employees: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated UPDATE employees SQL: %s\nArgs: %v\n", sql, args)
|
|
employeesResult, err := qb.ExecuteUpdate(ctx, tx, "employees", employeesUpdateData, employeesFilter)
|
|
if err != nil {
|
|
return
|
|
}
|
|
employeesRowsAffected, _ := employeesResult.RowsAffected()
|
|
fmt.Printf("-> UPDATE employees: %d baris terpengaruh.\n", employeesRowsAffected)
|
|
|
|
// --- Validasi Akhir Transaksi ---
|
|
if salariesRowsAffected == 1 && employeesRowsAffected == 1 {
|
|
fmt.Println("-> Validasi BERHASIL: Kedua tabel berhasil diperbarui.")
|
|
} else {
|
|
err = fmt.Errorf("validasi GAGAL: diharapkan 1 baris terupdate di setiap tabel, tetapi mendapat %d (salaries) dan %d (employees)", salariesRowsAffected, employeesRowsAffected)
|
|
return
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 3: TRANSAKSI MONGODB
|
|
// =============================================================================
|
|
|
|
// mongoTransactionExample demonstrates MongoDB transactions using the query builder.
|
|
// It prints the filters and update operations before executing them in a transaction.
|
|
// Expected output: Prints MongoDB filters and update operations for salaries and employees, transaction commit/abort status, and validation results.
|
|
// Example raw queries:
|
|
// MongoDB filters: {"employee_id": 123}
|
|
// MongoDB updates: {"$set": {"salary": 75000}}, {"$set": {"last_name": "Doe"}}
|
|
func mongoTransactionExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
employeeID := 123
|
|
newSalary := 75000
|
|
newLastName := "Doe"
|
|
|
|
client, err := dbService.GetMongoClient("mongodb")
|
|
if err != nil {
|
|
log.Fatalf("Gagal mendapatkan klien MongoDB: %v", err)
|
|
}
|
|
|
|
salariesCollection := client.Database("company_db").Collection("salaries")
|
|
employeesCollection := client.Database("company_db").Collection("employees")
|
|
|
|
session, err := client.StartSession()
|
|
if err != nil {
|
|
log.Fatalf("Gagal memulai sesi MongoDB: %v", err)
|
|
}
|
|
defer session.EndSession(ctx)
|
|
|
|
fmt.Printf("Memulai transaksi MongoDB untuk employee_id: %d\n", employeeID)
|
|
|
|
_, err = session.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
|
|
// --- Operasi 1: Update gaji di koleksi 'salaries' ---
|
|
fmt.Println("\n--- Operasi 1: UPDATE salaries ---")
|
|
salariesFilter := bson.M{"employee_id": employeeID}
|
|
salariesUpdate := bson.M{"$set": bson.M{"salary": newSalary}}
|
|
fmt.Printf("-> MongoDB Update Salaries Filter: %#v\n", salariesFilter)
|
|
fmt.Printf("-> MongoDB Update Salaries Operation: %#v\n", salariesUpdate)
|
|
|
|
salariesResult, err := salariesCollection.UpdateOne(sessCtx, salariesFilter, salariesUpdate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("gagal update koleksi salaries: %w", err)
|
|
}
|
|
fmt.Printf("-> UPDATE salaries: %d dokumen cocok (matched).\n", salariesResult.MatchedCount)
|
|
|
|
// --- Operasi 2: Update informasi di koleksi 'employees' ---
|
|
fmt.Println("\n--- Operasi 2: UPDATE employees ---")
|
|
employeesFilter := bson.M{"employee_id": employeeID}
|
|
employeesUpdate := bson.M{"$set": bson.M{"last_name": newLastName}}
|
|
fmt.Printf("-> MongoDB Update Employees Filter: %#v\n", employeesFilter)
|
|
fmt.Printf("-> MongoDB Update Employees Operation: %#v\n", employeesUpdate)
|
|
|
|
employeesResult, err := employeesCollection.UpdateOne(sessCtx, employeesFilter, employeesUpdate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("gagal update koleksi employees: %w", err)
|
|
}
|
|
fmt.Printf("-> UPDATE employees: %d dokumen cocok (matched).\n", employeesResult.MatchedCount)
|
|
|
|
// --- Validasi Akhir Transaksi ---
|
|
if salariesResult.MatchedCount == 1 && employeesResult.MatchedCount == 1 {
|
|
fmt.Println("-> Validasi BERHASIL: Kedua koleksi berhasil diperbarui.")
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("validasi GAGAL: diharapkan 1 dokumen terupdate di setiap koleksi, tetapi mendapat %d (salaries) dan %d (employees)", salariesResult.MatchedCount, employeesResult.MatchedCount)
|
|
})
|
|
|
|
if err != nil {
|
|
fmt.Printf("Transaksi MongoDB dibatalkan (ABORT) karena error: %v\n", err)
|
|
} else {
|
|
fmt.Println("Transaksi MongoDB berhasil di-commit.")
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 4: FILTER DAN PAGINASI
|
|
// =============================================================================
|
|
|
|
// filterAndPaginationExample demonstrates querying with filters and pagination.
|
|
// It builds and prints the SELECT query before executing it.
|
|
// Expected output: Prints SELECT SQL with filters and pagination, and the number of active users found.
|
|
// Example raw query:
|
|
// SELECT id, name FROM users WHERE (status = $1 AND created_at > $2) ORDER BY name ASC LIMIT 5 OFFSET 10
|
|
func filterAndPaginationExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
db, err := dbService.GetSQLXDB("main")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan koneksi DB: %v", err)
|
|
return
|
|
}
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
|
|
query := query.DynamicQuery{
|
|
Fields: []query.SelectField{
|
|
{Expression: "id"},
|
|
{Expression: "name"},
|
|
},
|
|
From: "users",
|
|
Filters: []query.FilterGroup{
|
|
{
|
|
LogicOp: "AND",
|
|
Filters: []query.DynamicFilter{
|
|
{Column: "status", Operator: query.OpEqual, Value: "active"},
|
|
{Column: "created_at", Operator: query.OpGreaterThan, Value: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)},
|
|
},
|
|
},
|
|
},
|
|
Sort: []query.SortField{{Column: "name", Order: "ASC"}},
|
|
Limit: 5,
|
|
Offset: 10,
|
|
}
|
|
|
|
var users []User
|
|
sql, args, err := qb.BuildQuery(query)
|
|
if err != nil {
|
|
log.Printf("Error building SELECT: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated SELECT SQL: %s\nArgs: %v\n", sql, args)
|
|
err = qb.ExecuteQuery(ctx, db, query, &users)
|
|
if err != nil {
|
|
log.Printf("Error query dengan filter: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> Filter & Paginasi: Ditemukan %d user aktif (halaman 3).\n", len(users))
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 5: QUERY DENGAN JOIN
|
|
// =============================================================================
|
|
|
|
// joinExample demonstrates querying with JOIN operations.
|
|
// It builds and prints the JOIN query before executing it.
|
|
// Expected output: Prints JOIN SQL query and the number of posts with author names found.
|
|
// Example raw query:
|
|
// SELECT p.id AS post_id, p.title, u.name AS author_name FROM posts p INNER JOIN users u ON p.user_id = u.id LIMIT 10
|
|
func joinExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
db, err := dbService.GetSQLXDB("main")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan koneksi DB: %v", err)
|
|
return
|
|
}
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
|
|
query := query.DynamicQuery{
|
|
Fields: []query.SelectField{
|
|
{Expression: "p.id", Alias: "post_id"},
|
|
{Expression: "p.title"},
|
|
{Expression: "u.name", Alias: "author_name"},
|
|
},
|
|
From: "posts",
|
|
Aliases: "p",
|
|
Joins: []query.Join{
|
|
{
|
|
Type: "INNER",
|
|
Table: "users",
|
|
Alias: "u",
|
|
OnConditions: query.FilterGroup{
|
|
Filters: []query.DynamicFilter{
|
|
{Column: "p.user_id", Operator: query.OpEqual, Value: "u.id"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Limit: 10,
|
|
}
|
|
|
|
var results []struct {
|
|
PostID int `db:"post_id"`
|
|
Title string `db:"title"`
|
|
AuthorName string `db:"author_name"`
|
|
}
|
|
sql, args, err := qb.BuildQuery(query)
|
|
if err != nil {
|
|
log.Printf("Error building JOIN: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated JOIN SQL: %s\nArgs: %v\n", sql, args)
|
|
err = qb.ExecuteQuery(ctx, db, query, &results)
|
|
if err != nil {
|
|
log.Printf("Error query JOIN: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> JOIN: Ditemukan %d post dengan nama penulis.\n", len(results))
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 6: QUERY DENGAN CTE
|
|
// =============================================================================
|
|
|
|
// cteExample demonstrates querying with Common Table Expressions (CTE).
|
|
// It builds and prints the CTE query before executing it.
|
|
// Expected output: Prints CTE SQL query and the number of users with more than 5 posts.
|
|
func cteExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
db, err := dbService.GetSQLXDB("main")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan koneksi DB: %v", err)
|
|
return
|
|
}
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
|
|
query := query.DynamicQuery{
|
|
CTEs: []query.CTE{
|
|
{
|
|
Name: "user_post_counts",
|
|
Query: query.DynamicQuery{
|
|
Fields: []query.SelectField{
|
|
{Expression: "user_id"},
|
|
{Expression: "COUNT(*)", Alias: "post_count"},
|
|
},
|
|
From: "posts",
|
|
GroupBy: []string{"user_id"},
|
|
},
|
|
},
|
|
},
|
|
Fields: []query.SelectField{
|
|
{Expression: "u.name"},
|
|
{Expression: "upc.post_count"},
|
|
},
|
|
From: "users u",
|
|
Joins: []query.Join{
|
|
{
|
|
Type: "INNER",
|
|
Table: "user_post_counts",
|
|
Alias: "upc",
|
|
OnConditions: query.FilterGroup{
|
|
Filters: []query.DynamicFilter{
|
|
{Column: "u.id", Operator: query.OpEqual, Value: "upc.user_id"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Filters: []query.FilterGroup{
|
|
{
|
|
Filters: []query.DynamicFilter{
|
|
{Column: "upc.post_count", Operator: query.OpGreaterThan, Value: 5},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var results []struct {
|
|
Name string `db:"name"`
|
|
PostCount int `db:"post_count"`
|
|
}
|
|
sql, args, err := qb.BuildQuery(query)
|
|
if err != nil {
|
|
log.Printf("Error building CTE: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated CTE SQL: %s\nArgs: %v\n", sql, args)
|
|
err = qb.ExecuteQuery(ctx, db, query, &results)
|
|
if err != nil {
|
|
log.Printf("Error query CTE: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> CTE: Ditemukan %d user dengan lebih dari 5 post.\n", len(results))
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 7: WINDOW FUNCTION
|
|
// =============================================================================
|
|
|
|
// windowFunctionExample demonstrates querying with window functions.
|
|
// It builds and prints the window function query before executing it.
|
|
// Expected output: Prints window function SQL query and the number of employees with salary rankings.
|
|
func windowFunctionExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
db, err := dbService.GetSQLXDB("main")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan koneksi DB: %v", err)
|
|
return
|
|
}
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
|
|
query := query.DynamicQuery{
|
|
Fields: []query.SelectField{
|
|
{Expression: "name"},
|
|
{Expression: "department"},
|
|
{Expression: "salary"},
|
|
},
|
|
From: "employees",
|
|
WindowFunctions: []query.WindowFunction{
|
|
{
|
|
Function: "RANK",
|
|
Over: "department",
|
|
OrderBy: "salary DESC",
|
|
Alias: "salary_rank",
|
|
},
|
|
},
|
|
Filters: []query.FilterGroup{
|
|
{
|
|
Filters: []query.DynamicFilter{
|
|
{Column: "department", Operator: query.OpEqual, Value: "Engineering"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var results []struct {
|
|
Name string `db:"name"`
|
|
Department string `db:"department"`
|
|
Salary float64 `db:"salary"`
|
|
SalaryRank int `db:"salary_rank"`
|
|
}
|
|
sql, args, err := qb.BuildQuery(query)
|
|
if err != nil {
|
|
log.Printf("Error building Window Function: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated Window Function SQL: %s\nArgs: %v\n", sql, args)
|
|
err = qb.ExecuteQuery(ctx, db, query, &results)
|
|
if err != nil {
|
|
log.Printf("Error query Window Function: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> Window Function: Ditemukan %d employee di departemen Engineering dengan peringkat gaji.\n", len(results))
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 8: VALIDASI DATA DINAMIS
|
|
// =============================================================================
|
|
|
|
// validationExample demonstrates dynamic data validation using the query builder.
|
|
// It builds and prints the validation query before executing it.
|
|
// Expected output: Prints validation SQL query and whether the email is duplicate or available.
|
|
func validationExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
db, err := dbService.GetSQLXDB("main")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan koneksi DB: %v", err)
|
|
return
|
|
}
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
validator := validation.NewDynamicValidator(qb)
|
|
|
|
userData := map[string]interface{}{"email": "test@example.com"}
|
|
emailRule := validation.NewUniqueFieldRule("users", "email")
|
|
|
|
// Build and print the validation query
|
|
countQuery := query.DynamicQuery{
|
|
From: "users",
|
|
Filters: []query.FilterGroup{{
|
|
Filters: []query.DynamicFilter{{Column: "email", Operator: query.OpEqual, Value: "test@example.com"}},
|
|
}},
|
|
}
|
|
sql, args, err := qb.BuildCountQuery(countQuery)
|
|
if err != nil {
|
|
log.Printf("Error building validation query: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated Validation SQL: %s\nArgs: %v\n", sql, args)
|
|
|
|
isDuplicate, err := validator.Validate(ctx, db, emailRule, userData)
|
|
if err != nil {
|
|
log.Printf("Error validasi: %v", err)
|
|
return
|
|
}
|
|
|
|
if isDuplicate {
|
|
fmt.Println("-> Validasi: Email 'test@example.com' sudah ada.")
|
|
} else {
|
|
fmt.Println("-> Validasi: Email 'test@example.com' tersedia.")
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 9: OPERASI JSON
|
|
// =============================================================================
|
|
|
|
// jsonQueryExample demonstrates JSON operations in queries.
|
|
// It builds and prints the JSON queries before executing them.
|
|
// Expected output: Prints JSON SELECT and UPDATE SQL queries, number of employees found, and update success message.
|
|
func jsonQueryExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
db, err := dbService.GetSQLXDB("main")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan koneksi DB: %v", err)
|
|
return
|
|
}
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
|
|
query := query.DynamicQuery{
|
|
Fields: []query.SelectField{{Expression: "*"}},
|
|
From: "employees",
|
|
Filters: []query.FilterGroup{{
|
|
Filters: []query.DynamicFilter{
|
|
{
|
|
Column: "metadata",
|
|
Operator: query.OpJsonEqual,
|
|
Value: "Engineering",
|
|
Options: map[string]interface{}{"path": "department"},
|
|
},
|
|
},
|
|
}},
|
|
}
|
|
|
|
var employees []Employee
|
|
sql, args, err := qb.BuildQuery(query)
|
|
if err != nil {
|
|
log.Printf("Error building JSON query: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated JSON SELECT SQL: %s\nArgs: %v\n", sql, args)
|
|
err = qb.ExecuteQuery(ctx, db, query, &employees)
|
|
if err != nil {
|
|
log.Printf("Error query JSON: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> Operasi JSON: Ditemukan %d employee di departemen Engineering (dari metadata JSON).\n", len(employees))
|
|
|
|
updateData := query.UpdateData{
|
|
JsonUpdates: map[string]query.JsonUpdate{
|
|
"metadata": {Path: "role", Value: "Senior Developer"},
|
|
},
|
|
}
|
|
filter := []query.FilterGroup{{Filters: []query.DynamicFilter{{Column: "id", Operator: query.OpEqual, Value: 1}}}}
|
|
sql, args, err = qb.BuildUpdateQuery("employees", updateData, filter)
|
|
if err != nil {
|
|
log.Printf("Error building JSON update: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated JSON UPDATE SQL: %s\nArgs: %v\n", sql, args)
|
|
_, err = qb.ExecuteUpdate(ctx, db, "employees", updateData, filter)
|
|
if err != nil {
|
|
log.Printf("Error update JSON: %v", err)
|
|
return
|
|
}
|
|
fmt.Println("-> Operasi JSON: Berhasil memperbarui 'role' di metadata untuk employee ID 1.")
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 10: QUERY MONGODB
|
|
// =============================================================================
|
|
|
|
// mongodbExample demonstrates MongoDB queries using the query builder.
|
|
// It prints the built filters and pipelines before executing them.
|
|
// Expected output: Prints MongoDB find filter, number of active users, aggregation pipeline, and number of departments.
|
|
func mongodbExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
client, err := dbService.GetMongoClient("mongodb")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan klien MongoDB: %v", err)
|
|
return
|
|
}
|
|
collection := client.Database("company_db").Collection("users")
|
|
mqb := query.NewMongoQueryBuilder()
|
|
|
|
// --- FIND ---
|
|
fmt.Println("\n--- Operasi FIND ---")
|
|
findQuery := query.DynamicQuery{
|
|
Filters: []query.FilterGroup{{Filters: []query.DynamicFilter{{Column: "status", Operator: query.OpEqual, Value: "active"}}}},
|
|
Limit: 5,
|
|
}
|
|
filter, _, _ := mqb.BuildFindQuery(findQuery)
|
|
fmt.Printf("-> MongoDB Find Filter: %#v\n", filter)
|
|
|
|
var users []User
|
|
err = mqb.ExecuteFind(ctx, collection, findQuery, &users)
|
|
if err != nil {
|
|
log.Printf("Error MongoDB Find: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> MongoDB Find: Ditemukan %d user aktif.\n", len(users))
|
|
|
|
// --- AGGREGATION ---
|
|
fmt.Println("\n--- Operasi AGGREGATION ---")
|
|
aggQuery := query.DynamicQuery{
|
|
Fields: []query.SelectField{
|
|
{Expression: "department", Alias: "_id"},
|
|
{Expression: "COUNT(*)", Alias: "count"},
|
|
},
|
|
GroupBy: []string{"department"},
|
|
}
|
|
pipeline, _ := mqb.BuildAggregateQuery(aggQuery)
|
|
fmt.Printf("-> MongoDB Aggregation Pipeline: %#v\n", pipeline)
|
|
|
|
var aggResults []struct {
|
|
ID string `bson:"_id"`
|
|
Count int `bson:"count"`
|
|
}
|
|
err = mqb.ExecuteAggregate(ctx, collection, aggQuery, &aggResults)
|
|
if err != nil {
|
|
log.Printf("Error MongoDB Aggregate: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> MongoDB Aggregate: Ditemukan user di %d departemen.\n", len(aggResults))
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 11: PENGGUNAAN READ REPLICA
|
|
// =============================================================================
|
|
|
|
// readReplicaExample demonstrates using read replicas for queries.
|
|
// It builds and prints the count query before executing it on the read replica.
|
|
// Expected output: Prints COUNT SQL query and the total number of users from the read replica.
|
|
// Example raw query:
|
|
// SELECT COUNT(*) FROM users
|
|
func readReplicaExample(dbService database.Service) {
|
|
ctx := context.Background()
|
|
readDB, err := dbService.GetReadDB("main")
|
|
if err != nil {
|
|
log.Printf("Gagal mendapatkan read replica: %v", err)
|
|
return
|
|
}
|
|
readxDB := sqlx.NewDb(readDB, "pgx")
|
|
qb := query.NewQueryBuilder(query.DBTypePostgreSQL)
|
|
|
|
countQuery := query.DynamicQuery{From: "users"}
|
|
sql, args, err := qb.BuildCountQuery(countQuery)
|
|
if err != nil {
|
|
log.Printf("Error building count query: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("Generated COUNT SQL: %s\nArgs: %v\n", sql, args)
|
|
count, err := qb.ExecuteCount(ctx, readxDB, countQuery)
|
|
if err != nil {
|
|
log.Printf("Error query di read replica: %v", err)
|
|
return
|
|
}
|
|
fmt.Printf("-> Read Replica: Total user (dari read replica): %d\n", count)
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 12: HEALTH CHECK DATABASE
|
|
// =============================================================================
|
|
|
|
// healthCheckExample demonstrates database health checks.
|
|
// It prints the health status of all databases.
|
|
// Expected output: Prints health status for each database (up/down with type or error).
|
|
func healthCheckExample(dbService database.Service) {
|
|
healthStatus := dbService.Health()
|
|
fmt.Println("-> Health Check Status:")
|
|
for dbName, status := range healthStatus {
|
|
if status["status"] == "up" {
|
|
fmt.Printf(" - Database %s: SEHAT (%s)\n", dbName, status["type"])
|
|
} else {
|
|
fmt.Printf(" - Database %s: TIDAK SEHAT - %s\n", dbName, status["error"])
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTOH 13: PARSING QUERY DARI URL
|
|
// =============================================================================
|
|
|
|
// urlQueryParsingExample demonstrates parsing query parameters from URL.
|
|
// It parses the URL query and prints the resulting dynamic query structure.
|
|
// Expected output: Prints parsed fields, filters, sort, and limit from the URL query.
|
|
func urlQueryParsingExample(dbService database.Service) {
|
|
values := url.Values{}
|
|
values.Set("fields", "id,name")
|
|
values.Set("filter[status][_eq]", "active")
|
|
values.Set("filter[age][_gt]", "25")
|
|
values.Set("sort", "-name")
|
|
values.Set("limit", "10")
|
|
|
|
parser := query.NewQueryParser()
|
|
dynamicQuery, err := parser.ParseQuery(values, "users")
|
|
if err != nil {
|
|
log.Printf("Error parsing URL query: %v", err)
|
|
return
|
|
}
|
|
|
|
fmt.Println("-> Parsing URL Query:")
|
|
fmt.Printf(" Fields: %v\n", dynamicQuery.Fields)
|
|
fmt.Printf(" Filters: %+v\n", dynamicQuery.Filters)
|
|
fmt.Printf(" Sort: %+v\n", dynamicQuery.Sort)
|
|
fmt.Printf(" Limit: %d\n", dynamicQuery.Limit)
|
|
}
|
|
|
|
// =============================================================================
|
|
// AKHIR FILE
|
|
// =============================================================================
|