An open API service indexing awesome lists of open source software.

https://github.com/piyushkumar96/generic-orm

A comprehensive, high-level ORM wrapper for Go built on top of GORM that provides a generic interface for database operations with PostgreSQL and other SQL databases.
https://github.com/piyushkumar96/generic-orm

generic go golang gorm mysql orm postgres postgresql sql

Last synced: 4 months ago
JSON representation

A comprehensive, high-level ORM wrapper for Go built on top of GORM that provides a generic interface for database operations with PostgreSQL and other SQL databases.

Awesome Lists containing this project

README

          

# Generic ORM

A comprehensive, high-level ORM wrapper for Go built on top of GORM that provides a generic interface for database operations with PostgreSQL and other SQL databases.

## Table of Contents

- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Usage Examples](#usage-examples)
- [API Reference](#api-reference)
- [Advanced Features](#advanced-features)
- [Testing](#testing)
- [Contributing](#contributing)
- [License](#license)

## Features

This generic ORM provides a unified interface for database operations with the following key features:

- **Generic Interface**: Consistent API across different models and database operations
- **CRUD Operations**: Complete support for Create, Read, Update, Delete operations
- **Transaction Management**: Built-in support for database transactions with rollback capabilities
- **Pagination Support**: Easy pagination with configurable page size and sorting
- **Batch Operations**: Efficient bulk create and upsert operations
- **Soft Deletes**: Built-in soft delete functionality with permanent delete options
- **Query Builder**: Flexible querying with conditions, raw SQL, and object-based queries
- **Connection Pooling**: Configurable connection pool management
- **Context Support**: Full context support for timeouts and cancellation
- **Error Handling**: Comprehensive error handling with custom error types
- **Debug Mode**: Built-in debug logging for development
- **PostgreSQL First**: Optimized for PostgreSQL with extensibility for other databases
- **Multi-Schema Support**: Built-in support for PostgreSQL schemas with context-based switching
- **Multi-Tenant Ready**: Schema-based tenant isolation for SaaS applications

## Installation

```bash
# Set up private module access if needed
export GOPRIVATE=github.com/piyushkumar96/generic-orm

# Install the package
go get github.com/piyushkumar96/generic-orm
```

## Quick Start

```go
package main

import (
"context"
"log"
"time"

orm "github.com/piyushkumar96/generic-orm"
"github.com/piyushkumar96/generic-orm/config"
"github.com/piyushkumar96/generic-orm/interfaces"
)

// Define your model
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Email string `gorm:"unique;not null"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `gorm:"index"`
}

func main() {
// 1. Create database connection
client := orm.SQLClient{
Config: config.DBConnectionConfigurations{
DatabaseName: "myapp",
DatabaseUser: "postgres",
DatabasePassword: "password",
DatabaseHost: "localhost",
DatabasePort: 5432,
DatabaseSchema: "public",
DatabaseDriver: "postgres",
MaxOpenConnections: 100,
MaxIdleConnections: 10,
ConnMaxLifetime: 1 * time.Hour,
},
}

db, err := client.Init()
if err != nil {
log.Fatal("Failed to connect to database:", err)
}

// 2. Initialize ORM handler
userHandler := &orm.SQLGORM{
DB: db,
DataModel: &User{},
Config: config.GORMConfig{
AppPaginationDefaultPageSize: 10,
AppPaginationDefaultPageIndex: 1,
AppPaginationDefaultOrderBy: []string{"id", "desc"},
DebugModeEnabled: true,
DBOperationDefaultTimeoutInSecond: 30 * time.Second,
},
}

// 3. Create a new user
ctx := context.Background()
newUser := &User{
Name: "John Doe",
Email: "john@example.com",
}

meta, err := userHandler.CreateRecord(ctx, newUser)
if err != nil {
log.Fatal("Failed to create user:", err)
}
log.Printf("Created user, affected rows: %d", meta.RowsAffected)
}
```

## Configuration

### Database Connection Configuration

```go
type DBConnectionConfigurations struct {
DatabaseName string // Database name
DatabaseUser string // Database username
DatabasePassword string // Database password
DatabaseHost string // Database host
DatabasePort int // Database port
DatabaseSchemas string // Database schemas comma separated (for PostgreSQL)
DatabaseDriver string // Database driver ("postgres")
MaxOpenConnections int // Maximum open connections
MaxIdleConnections int // Maximum idle connections
ConnMaxLifetime time.Duration // Connection maximum lifetime
}
```

### GORM Configuration

```go
type GORMConfig struct {
AppPaginationDefaultPageSize int // Default page size (e.g., 10)
AppPaginationDefaultPageIndex int // Default page index (e.g., 1)
AppPaginationDefaultOrderBy []string // Default ordering (e.g., []string{"id", "desc"})
DebugModeEnabled bool // Enable debug logging
DBOperationDefaultTimeoutInSecond time.Duration // Default timeout for operations
}
```

## Usage Examples

For comprehensive examples, see the [examples/](examples/) directory:
- **Basic Examples**: [examples/examples.go](examples/examples.go) - Complete CRUD, transactions, pagination, etc.
- **Multi-Schema Examples**: [examples/multi-schema-example.go](examples/multi-schema-example.go) - Multi-tenant schema isolation
- **Build Instructions**: [examples/README.md](examples/README.md) - How to build and run examples

Here are some key usage patterns:

### Basic CRUD Operations

```go
// Create
user := &User{Name: "Alice", Email: "alice@example.com"}
meta, err := handler.CreateRecord(ctx, user)

// Read by ID
var user User
meta, err := handler.GetRecordByID(ctx, &user, 1)

// Update
user.Name = "Alice Updated"
meta, err := handler.UpdateRecord(ctx, &user)

// Soft Delete
updates := map[string]interface{}{"deleted_by": "admin"}
meta, err := handler.DeleteRecord(ctx, &user, updates)

// Permanent Delete
meta, err := handler.DeleteRecordPermanently(ctx, &user)
```

### Querying with Conditions

```go
// Query with WHERE conditions
var users []User
meta, err := handler.GetRecordsByWhereCondition(ctx, &users, "name LIKE ?", "%john%")

// Query with object conditions
query := User{Active: true}
meta, err := handler.GetRecordsByObject(ctx, &users, query)

// Raw SQL query
var result []CustomResult
meta, err := handler.GetRecordByRawSQL(ctx, &result, "SELECT id, name FROM users WHERE created_at > ?", time.Now().AddDate(-1, 0, 0))
```

### Pagination

```go
pagination := &models.Pagination{
Records: &[]User{},
Page: 1,
Size: 10,
Enabled: true,
OrderBy: []string{"created_at desc"},
}

paginationResult, meta, err := handler.GetRecords(ctx, pagination)
log.Printf("Total records: %d, Page: %d/%d", paginationResult.Total, paginationResult.Page, (paginationResult.Total+int64(paginationResult.Size)-1)/int64(paginationResult.Size))
```

### Transaction Management

```go
// Define transaction operations
dbTxns := []*models.DBTxn{
{
Op: models.Create,
Model: &User{},
Record: &User{Name: "Transaction User 1", Email: "tx1@example.com"},
},
{
Op: models.Update,
Model: &User{},
Record: &User{ID: 1, Name: "Updated Name"},
},
{
Op: models.Delete,
Model: &User{},
Record: &User{ID: 2},
},
}

// Execute transaction
metas, err := handler.ExecuteDBTxn(ctx, dbTxns)
if err != nil {
log.Fatal("Transaction failed:", err)
}
```

### Multi-Schema/Multi-Tenant Operations

```go
import "github.com/piyushkumar96/generic-orm/helper"

// Set schema in context for tenant isolation
tenantACtx := helper.SetSchemaInContext(ctx, "tenant_a")
tenantBCtx := helper.SetSchemaInContext(ctx, "tenant_b")

// Create user in Tenant A schema
userA := &User{Name: "Alice", Email: "alice@tenanta.com"}
meta, err := handler.CreateRecord(tenantACtx, userA)

// Create user in Tenant B schema (completely isolated)
userB := &User{Name: "Bob", Email: "bob@tenantb.com"}
meta, err = handler.CreateRecord(tenantBCtx, userB)

// Retrieve schema from context
if schema, ok := helper.GetSchemaFromContext(tenantACtx); ok {
fmt.Printf("Current schema: %s", schema)
}

// Set multiple schemas in search path
multiSchemaCtx := helper.SetMultipleSchemasInContext(ctx, []string{"tenant_a", "tenant_b", "public"})
```

## API Reference

### Core Interface Methods

The `IORMHandler` interface provides the following methods:

#### Create Operations
- `CreateRecord(ctx, record)` - Create a single record
- `CreateRecordInBatches(ctx, record, batchSize)` - Create records in batches
- `UpsertRecord(ctx, record)` - Insert or update on conflict
- `UpsertRecordInBatches(ctx, record, batchSize)` - Batch upsert operations

#### Read Operations
- `GetRecordByID(ctx, record, id)` - Get single record by primary key
- `GetRecords(ctx, pagination)` - Get records with pagination
- `GetRecordsWithoutPagination(ctx, record)` - Get all records without pagination
- `GetRecordByWhereCondition(ctx, record, condition, values...)` - Query with WHERE clause
- `GetRecordByObject(ctx, record, object)` - Query using object as condition
- `GetRecordsByObject(ctx, record, query, values...)` - Query multiple records by object
- `GetRecordsByWhereCondition(ctx, record, condition, values...)` - Query multiple with WHERE
- `GetRecordByRawSQL(ctx, record, sql, values...)` - Execute raw SQL query

#### Update Operations
- `UpdateRecord(ctx, record)` - Update record with all fields
- `UpdateRecordWithCondition(ctx, record, query)` - Conditional update
- `UpdateRecordWithSelectedColumns(ctx, record, updates)` - Update specific columns

#### Delete Operations
- `DeleteRecord(ctx, record, updates)` - Soft delete with optional updates
- `DeleteRecordWithCondition(ctx, record, updates, query)` - Conditional soft delete
- `DeleteRecordPermanently(ctx, record)` - Permanent delete
- `DeleteRecordPermanentlyWithCondition(ctx, record, query)` - Conditional permanent delete

#### Transaction Operations
- `ExecuteDBTxn(ctx, transactions)` - Execute multiple operations as transaction

#### Multi-Schema Operations
The ORM provides built-in support for PostgreSQL schemas through context-based schema switching:

```go
import "github.com/piyushkumar96/generic-orm/helper"

// Schema context functions
helper.SetSchemaInContext(ctx, "schema_name") // Set single schema
helper.GetSchemaFromContext(ctx) // Get schema from context
helper.SetMultipleSchemasInContext(ctx, []string{...}) // Set search path with multiple schemas
```

## Advanced Features

### Custom Error Handling

The package includes comprehensive error handling with custom error types that wrap GORM errors and provide additional context.

### Debug Mode

Enable debug mode to see all SQL queries and execution details:

```go
config := config.GORMConfig{
DebugModeEnabled: true,
// other config...
}
```

### Context Timeouts

All operations support context-based timeouts and cancellation:

```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

meta, err := handler.CreateRecord(ctx, record)
```

### Connection Pool Management

Configure connection pooling for optimal performance:

```go
config := config.DBConnectionConfigurations{
MaxOpenConnections: 100, // Maximum open connections
MaxIdleConnections: 10, // Maximum idle connections
ConnMaxLifetime: 1 * time.Hour, // Connection lifetime
// other config...
}
```

### Multi-Schema Support

The ORM provides comprehensive multi-schema support for PostgreSQL, enabling schema-based multi-tenancy:

#### Schema Context Management

```go
import "github.com/piyushkumar96/generic-orm/helper"

// Set schema for operations
ctx := helper.SetSchemaInContext(context.Background(), "tenant_schema")

// All database operations will use the specified schema
user := &User{Name: "John", Email: "john@tenant.com"}
meta, err := handler.CreateRecord(ctx, user)

// Retrieve current schema
if schema, ok := helper.GetSchemaFromContext(ctx); ok {
fmt.Printf("Current schema: %s", schema)
}
```

#### Multi-Tenant Isolation

```go
// Tenant A operations
tenantACtx := helper.SetSchemaInContext(ctx, "tenant_a")
userA := &User{Name: "Alice", TenantID: "tenant-a"}
handler.CreateRecord(tenantACtx, userA)

// Tenant B operations (completely isolated)
tenantBCtx := helper.SetSchemaInContext(ctx, "tenant_b")
userB := &User{Name: "Bob", TenantID: "tenant-b"}
handler.CreateRecord(tenantBCtx, userB)

// Each tenant's data is isolated in separate schemas
```

#### Multiple Schema Search Path

```go
// Set multiple schemas in search path
schemas := []string{"tenant_a", "tenant_b", "public"}
multiCtx := helper.SetMultipleSchemasInContext(ctx, schemas)

// Operations will search across all specified schemas
var users []User
handler.GetRecordsWithoutPagination(multiCtx, &users)
```

#### Database Configuration for Multi-Schema

```go
config := config.DBConnectionConfigurations{
DatabaseSchemas: "tenant_a,tenant_b,tenant_c,public", // Comma-separated schemas
// other config...
}
```

## Testing

The package includes comprehensive test coverage. Run tests with:

```bash
go test ./...
```

For integration tests with a real database:

```bash
# Set up test database environment variables
export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=test_db
export DB_USER=postgres
export DB_PASSWORD=password

go test -tags=integration ./...
```

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.