Skip to content

yaroher/protoc-gen-pgx-orm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

27 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

protoc-gen-pgx-orm

protoc-gen-pgx-orm is a powerful Protocol Buffers plugin that automatically generates type-safe ORM (Object-Relational Mapping) for PostgreSQL using the PGX v5 library. The project allows creating complete SQL schemas and Go code for database operations based on protobuf definitions.

πŸš€ Feature Overview

Core Features:

  • Automatic SQL DDL generation from protobuf messages
  • Type-safe Go ORM with full PGX v5 support
  • Support for all major PostgreSQL SQL types
  • Table relationships (One-to-Many, Many-to-Many)
  • Virtual fields and computed columns
  • Embedded messages support
  • OneOf fields with automatic serialization
  • Arrays and complex data types
  • Custom casters for type transformations
  • Connection pools and transactions

Supported SQL Types:

  • TEXT, INTEGER, BIGINT, SMALLINT
  • DOUBLE_PRECISION, REAL
  • BOOLEAN, TIMESTAMPTZ
  • CHAR, JSONB, HSTORE
  • Arrays of all supported types

πŸ“‹ Table of Contents

  1. Installation
  2. Quick Start
  3. Project Architecture
  4. Protobuf Syntax
  5. Code Generation
  6. Using the ORM
  7. Advanced Features
  8. Examples
  9. API Reference

πŸ›  Installation

Requirements:

  • Go 1.24.3+
  • Protocol Buffers compiler (protoc)
  • PostgreSQL

Install the plugin:

go install github.com/yaroher/protoc-gen-pgx-orm@latest

Build from source:

git clone https://github.com/yaroher/protoc-gen-pgx-orm.git
cd protoc-gen-pgx-orm
make build

πŸš€ Quick Start

1. Create a protobuf file

syntax = "proto3";

package example;
import "protopgx/pgx.proto";

message User {
  option (sql.sql_table) = {
    generate: true
    table_name: "users"
    virtual_fields: [
      {
        sql_name: "created_at"
        sql_type: {type: TIMESTAMPTZ}
        constraints: {default_value: "NOW()"}
        is_nullable: false
      }
    ]
  };

  int64 id = 1 [(sql.sql_field) = {
    constraints: {primary_key: true}
    sql_type: {type: BIGINT}
  }];

  string email = 2 [(sql.sql_field) = {
    constraints: {unique: true}
    sql_type: {type: TEXT}
  }];

  string name = 3 [(sql.sql_field) = {
    sql_type: {type: TEXT}
    constraints: {constraint: "NOT NULL"}
  }];
}

2. Generate the code

protoc --plugin=./protoc-gen-pgx-orm \
  --pgx-orm_out=./generated \
  --pgx-orm_opt=paths=source_relative,sql_file=./generated/models.sql,orm_folder=./generated/orm \
  --proto_path=. your_proto_file.proto

3. Use the generated ORM

package main

import (
    "context"
    "github.com/jackc/pgx/v5/pgxpool"
    "yourproject/generated/orm"
    "yourproject/proto"
)

func main() {
    ctx := context.Background()

    // Database connection
    pool, err := pgxpool.New(ctx, "postgres://user:password@localhost/dbname")
    if err != nil {
        panic(err)
    }
    defer pool.Close()

    // Create repository
    userRepo := orm.NewUserRepository(func(ctx context.Context, op orm.SqlOpType) orm.DB {
        return pool
    })

    // Create user
    user := &proto.User{
        Email: "user@example.com",
        Name:  "John Doe",
    }

    // Insert into database
    err = userRepo.Insert(ctx, user)
    if err != nil {
        panic(err)
    }

    // Find user
    foundUsers, err := userRepo.Select().
        Where(orm.User.Email.Equals("user@example.com")).
        Query(ctx)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Found user: %+v\n", foundUsers[0])
}

πŸ— Project Architecture

Directory Structure:

protoc-gen-pgx-orm/
β”œβ”€β”€ main.go              # Plugin entry point
β”œβ”€β”€ protopgx/           # Protobuf definitions for SQL annotations
β”‚   β”œβ”€β”€ pgx.proto       # Main options for fields and tables
β”‚   └── pgx.pb.go       # Generated Go code
β”œβ”€β”€ orm/                # ORM generator core
β”‚   β”œβ”€β”€ generator.go    # Go code generator
β”‚   β”œβ”€β”€ table.go        # Table abstractions
β”‚   β”œβ”€β”€ select.go       # SELECT queries
β”‚   β”œβ”€β”€ insert.go       # INSERT queries
β”‚   β”œβ”€β”€ update.go       # UPDATE queries
β”‚   β”œβ”€β”€ delete.go       # DELETE queries
β”‚   β”œβ”€β”€ casters.go      # Type converters
β”‚   β”œβ”€β”€ types.go        # Base types and interfaces
β”‚   β”œβ”€β”€ clause.go       # SQL conditions (WHERE, HAVING)
β”‚   β”œβ”€β”€ column.go       # Column abstractions
β”‚   β”œβ”€β”€ scanrepo.go     # Scanner repositories
β”‚   β”œβ”€β”€ protorepo.go    # Protobuf repositories
β”‚   └── tables.go.tmpl  # Table generation template
β”œβ”€β”€ tabletree/          # Table tree and relationships
β”‚   β”œβ”€β”€ tabletree.go    # Main tree building logic
β”‚   β”œβ”€β”€ field.go        # Field processing
β”‚   β”œβ”€β”€ sql_type.go     # SQL type mapping
β”‚   └── helpers.go      # Helper functions
β”œβ”€β”€ test/               # Test examples
β”‚   β”œβ”€β”€ test.proto      # Test protobuf file
β”‚   └── models.sql      # Generated SQL
└── help/               # Helper utilities
    └── help.go         # Logger and utilities

System Components:

1. protoc-gen-pgx-orm Plugin

  • Entry point for protoc
  • Parses protobuf files
  • Calls SQL and Go code generators

2. tabletree Package

  • Builds table tree from protobuf
  • Processes table relationships
  • Resolves dependencies and connections

3. orm Package

  • Generates type-safe Go code
  • Creates SQL queries
  • Provides database abstractions

4. protopgx Package

  • Defines protobuf options for SQL
  • Extends standard protobuf capabilities

πŸ“ Protobuf Syntax

Table Options

message MyTable {
  option (sql.sql_table) = {
    generate: true                    // Generate table
    table_name: "custom_table_name"   // Custom table name
    virtual_fields: [                 // Virtual fields
      {
        sql_name: "created_at"
        sql_type: {type: TIMESTAMPTZ}
        constraints: {default_value: "NOW()"}
        is_nullable: false
      }
    ]
    constraints: [                    // Table constraints
      "CONSTRAINT check_age CHECK (age >= 0)"
    ]
  };
}

Field Options

int64 id = 1 [(sql.sql_field) = {
  constraints: {primary_key: true}    // Primary key
  sql_type: {type: BIGINT}           // Explicit SQL type
}];

string email = 2 [(sql.sql_field) = {
  constraints: {unique: true}         // Unique field
  sql_type: {type: TEXT}
}];

string name = 3 [(sql.sql_field) = {
  sql_type: {type: TEXT}
  constraints: {constraint: "NOT NULL"} // Custom constraint
}];

repeated string tags = 4 [(sql.sql_field) = {
  sql_type: {type: TEXT}             // Array (automatic)
}];

// Skip field
string internal = 5 [(sql.sql_field) = {
  skip: true
}];

One-to-Many Relationships

message User {
  // ... user fields

  repeated Post posts = 10 [(sql.sql_relation) = {
    one_to_many: {
      ref_name: "user_id"                    // Foreign key name
      constraint: "FOREIGN KEY (user_id) REFERENCES users(id)"
      on_delete_cascade: true                // CASCADE on delete
    }
  }];
}

message Post {
  // ... post fields
  int64 user_id = 5 [(sql.sql_field) = {
    sql_type: {type: BIGINT}
  }];
}

Many-to-Many Relationships

message User {
  repeated Tag tags = 13 [(sql.sql_relation) = {
    many_to_many: {
      table: {                               // Junction table
        generate: true
        table_name: "user_tags"
      }
      ref_on_delete_cascade: true
      ref_constraint: "FOREIGN KEY (user_id) REFERENCES users(id)"
      back_ref_on_delete_cascade: false
      back_ref_constraint: "FOREIGN KEY (tag_id) REFERENCES tags(id)"
    }
  }];
}

Embedded Messages

message Address {
  string street = 1 [(sql.sql_field) = {sql_type: {type: TEXT}}];
  string city = 2 [(sql.sql_field) = {sql_type: {type: TEXT}}];
}

message User {
  // Embedded message (fields will be added to users table)
  Address address = 9 [(sql.sql_field) = {
    embedded_message: true
  }];

  // Serialized message (will be stored as JSONB)
  UserPreferences preferences = 10 [(sql.sql_field) = {
    serialized_message: true
    sql_type: {type: JSONB}
  }];
}

OneOf Fields

message Notification {
  oneof delivery_method {
    EmailDelivery email = 10 [(sql.sql_field) = {
      serialized_message: true
    }];
    SmsDelivery sms = 11 [(sql.sql_field) = {
      serialized_message: true
    }];
  }
}

Custom Casters

message User {
  Ulid ulid = 999 [(sql.sql_field) = {
    constraints: {primary_key: true}
    sql_type: {type: TEXT, user_cast: true}  // Requires custom caster
  }];
}

βš™ Code Generation

Generation Command

protoc --plugin=./protoc-gen-pgx-orm \
  --pgx-orm_out=<output_dir> \
  --pgx-orm_opt=<options> \
  --proto_path=<proto_path> \
  <proto_files>

Generator Options:

  • paths=source_relative - Use relative paths
  • sql_file=<path> - Path for SQL file generation
  • orm_folder=<path> - Folder for Go ORM code generation

Example with Makefile:

.PHONY: generate
generate: build
	protoc --plugin=./protoc-gen-pgx-orm \
		--pgx-orm_out=./generated \
		--pgx-orm_opt=paths=source_relative,sql_file=./generated/models.sql,orm_folder=./generated/orm \
		--proto_path=. $(shell find . -name "*.proto")

.PHONY: build
build:
	go build ./

πŸ”§ Using the ORM

Basic Operations

Creating a Repository

// For protobuf messages
userRepo := orm.NewUserRepository(dbGetter, customCaster1, customCaster2)

// For scanners (without protobuf)
scannerRepo := orm.NewUserScannerRepository(dbGetter)

INSERT Operations

// Simple insert
err := userRepo.Insert(ctx, user)

// Insert with returning fields
err := userRepo.Insert(ctx, user,
    orm.User.CreatedAt,
    orm.User.UpdatedAt,
)

// Insert with conflict handling
err := userRepo.Insert(ctx, user).
    OnConflict(orm.User.Email).
    DoUpdate(orm.User.Name, orm.User.UpdatedAt).
    Exec(ctx)

SELECT Operations

// Select all fields
users, err := userRepo.SelectAll().Query(ctx)

// Select specific fields
users, err := userRepo.Select(orm.User.Id, orm.User.Name).Query(ctx)

// With conditions
users, err := userRepo.Select().
    Where(
        orm.User.Age.GreaterThan(18),
        orm.User.Email.IsNotNull(),
    ).
    OrderByASC(orm.User.Name).
    Limit(10).
    Query(ctx)

// Single record
user, err := userRepo.Select().
    Where(orm.User.Id.Equals(123)).
    QueryRow(ctx)

// Aggregate functions
count, err := userRepo.Select(orm.User.Id.Count()).
    Where(orm.User.IsActive.IsTrue()).
    QueryRow(ctx)

UPDATE Operations

// Simple update
err := userRepo.Update().
    Set(
        orm.User.Name.Set("New Name"),
        orm.User.UpdatedAt.Set(time.Now()),
    ).
    Where(orm.User.Id.Equals(123)).
    Exec(ctx)

// Update with returning
updatedUser, err := userRepo.Update().
    Set(orm.User.Name.Set("Updated")).
    Where(orm.User.Id.Equals(123)).
    ReturningAll().
    QueryRow(ctx)

DELETE Operations

// Delete with condition
err := userRepo.Delete().
    Where(orm.User.Id.Equals(123)).
    Exec(ctx)

// Delete with returning
deletedUser, err := userRepo.Delete().
    Where(orm.User.Id.Equals(123)).
    ReturningAll().
    QueryRow(ctx)

Conditions and Operators

Basic Operators

// Equality
orm.User.Email.Equals("test@example.com")

// Comparisons
orm.User.Age.GreaterThan(18)
orm.User.Age.LessThanOrEqual(65)
orm.User.Balance.Between(100.0, 1000.0)

// IN / NOT IN
orm.User.Status.In([]string{"active", "pending"})
orm.User.Id.NotIn([]int64{1, 2, 3})

// NULL checks
orm.User.Email.IsNotNull()
orm.User.DeletedAt.IsNull()

// LIKE operators
orm.User.Name.StartsWith("John")
orm.User.Name.Contains("test")
orm.User.Name.EndsWith("@example.com")

Logical Operators

// AND (default)
orm.User.Age.GreaterThan(18).And(orm.User.Email.IsNotNull())

// OR
orm.User.Status.Equals("active").Or(orm.User.Status.Equals("pending"))

// Complex conditions
orm.User.And(
    orm.User.Age.GreaterThan(18),
    orm.User.Or(
        orm.User.Status.Equals("active"),
        orm.User.Email.IsNotNull(),
    ),
)

Subqueries

// EXISTS
orm.User.ExistsOf(
    orm.Post.Select().
        Where(orm.Post.UserId.Equals(orm.User.Id)),
)

// NOT EXISTS
orm.User.NotExistsOf(
    orm.Post.Select().
        Where(orm.Post.UserId.Equals(orm.User.Id)),
)

// Raw SQL
orm.User.ExistsRaw("SELECT 1 FROM posts WHERE user_id = $1", orm.User.Id)

Transactions

func TransferMoney(ctx context.Context, fromID, toID int64, amount float64) error {
    tx, err := pool.Begin(ctx)
    if err != nil {
        return err
    }
    defer tx.Rollback(ctx)

    // Use transaction
    dbGetter := func(ctx context.Context, op orm.SqlOpType) orm.DB {
        return tx
    }

    userRepo := orm.NewUserRepository(dbGetter)

    // Debit money
    err = userRepo.Update().
        Set(orm.User.Balance.Set(orm.User.Balance.Sub(amount))).
        Where(orm.User.Id.Equals(fromID)).
        Exec(ctx)
    if err != nil {
        return err
    }

    // Credit money
    err = userRepo.Update().
        Set(orm.User.Balance.Set(orm.User.Balance.Add(amount))).
        Where(orm.User.Id.Equals(toID)).
        Exec(ctx)
    if err != nil {
        return err
    }

    return tx.Commit(ctx)
}

🎯 Advanced Features

Custom Casters

For complex data types, you can define custom transformers:

// Define custom caster for ULID
func ULIDToString(ulid *Ulid) string {
    if ulid == nil {
        return ""
    }
    return ulid.Id
}

func StringToULID(s string) *Ulid {
    if s == "" {
        return nil
    }
    return &Ulid{Id: s}
}

// Usage in repository
userRepo := orm.NewUserRepository(
    dbGetter,
    ULIDToString,  // for ulid field
    StringToULID,  // for ulid field
)

Virtual Fields

Virtual fields don't exist in protobuf but are present in SQL:

message User {
  option (sql.sql_table) = {
    virtual_fields: [
      {
        sql_name: "full_name"
        sql_type: {type: TEXT}
        constraints: {constraint: "GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED"}
        is_nullable: false
      },
      {
        sql_name: "created_at"
        sql_type: {type: TIMESTAMPTZ}
        constraints: {default_value: "NOW()"}
        is_nullable: false
      }
    ]
  };
}

When working with virtual fields:

// For insert/update you need to pass virtual field values
err := userRepo.Insert(ctx, user,
    orm.User.WithUserCreatedAt(time.Now()),
)

Arrays and Complex Types

// Working with arrays
user := &proto.User{
    Tags: []string{"tag1", "tag2", "tag3"},
}

// Search by arrays
users, err := userRepo.Select().
    Where(orm.User.Tags.Contains("tag1")).
    Query(ctx)

// JSONB fields
preferences := &proto.UserPreferences{
    Theme:     "dark",
    Language:  "en",
}

user := &proto.User{
    Preferences: preferences,
}

OneOf Fields

OneOf fields are automatically serialized to JSONB:

notification := &proto.Notification{
    Title:   "Welcome",
    Message: "Welcome to our service!",
    DeliveryMethod: &proto.Notification_Email{
        Email: &proto.EmailDelivery{
            RecipientEmail: "user@example.com",
            Subject:        "Welcome",
        },
    },
}

// Search by oneof type
notifications, err := notificationRepo.Select().
    Where(orm.Notification.NotificationType.Equals("email")).
    Query(ctx)

Batch Operations

// Batch insert
users := []*proto.User{user1, user2, user3}
err := userRepo.InsertBatch(ctx, users)

// Batch update
err := userRepo.UpdateBatch(ctx, users,
    orm.User.Name,
    orm.User.UpdatedAt,
)

// CopyFrom for fast insertion
scanners := make([]*orm.UserScanner, len(users))
for i, user := range users {
    scanners[i] = userRepo.ProtoToScanner(user)
}

rowsAffected, err := userRepo.CopyFrom(ctx, db, scanners)

πŸ“š Examples

Complete Microservice Example

// user.proto
syntax = "proto3";

package user;
import "protopgx/pgx.proto";

message User {
  option (sql.sql_table) = {
    generate: true
    table_name: "users"
    virtual_fields: [
      {
        sql_name: "created_at"
        sql_type: {type: TIMESTAMPTZ}
        constraints: {default_value: "NOW()"}
        is_nullable: false
      },
      {
        sql_name: "updated_at"
        sql_type: {type: TIMESTAMPTZ}
        constraints: {default_value: "NOW()"}
        is_nullable: false
      }
    ]
    constraints: [
      "CONSTRAINT check_email CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$')"
    ]
  };

  int64 id = 1 [(sql.sql_field) = {
    constraints: {primary_key: true}
    sql_type: {type: BIGINT}
  }];

  string email = 2 [(sql.sql_field) = {
    constraints: {unique: true}
    sql_type: {type: TEXT}
  }];

  string first_name = 3 [(sql.sql_field) = {
    sql_type: {type: TEXT}
    constraints: {constraint: "NOT NULL"}
  }];

  string last_name = 4 [(sql.sql_field) = {
    sql_type: {type: TEXT}
    constraints: {constraint: "NOT NULL"}
  }];

  int32 age = 5 [(sql.sql_field) = {
    sql_type: {type: INTEGER}
    constraints: {default_value: "0"}
  }];

  bool is_active = 6 [(sql.sql_field) = {
    sql_type: {type: BOOLEAN}
    constraints: {default_value: "true"}
  }];
}
// service/user_service.go
package service

import (
    "context"
    "errors"
    "time"
    "github.com/jackc/pgx/v5/pgxpool"
    "yourproject/generated/orm"
    "yourproject/proto/user"
)

type UserService struct {
    pool *pgxpool.Pool
    repo orm.UserRepository
}

func NewUserService(pool *pgxpool.Pool) *UserService {
    dbGetter := func(ctx context.Context, op orm.SqlOpType) orm.DB {
        return pool
    }

    return &UserService{
        pool: pool,
        repo: orm.NewUserRepository(dbGetter),
    }
}

func (s *UserService) CreateUser(ctx context.Context, req *user.CreateUserRequest) (*user.User, error) {
    // Check if email exists
    existing, err := s.repo.Select().
        Where(orm.User.Email.Equals(req.Email)).
        QueryRow(ctx)
    if err == nil {
        return nil, errors.New("user already exists")
    }

    newUser := &user.User{
        Email:     req.Email,
        FirstName: req.FirstName,
        LastName:  req.LastName,
        Age:       int32(req.Age),
        IsActive:  true,
    }

    err = s.repo.Insert(ctx, newUser)
    if err != nil {
        return nil, err
    }

    return newUser, nil
}

func (s *UserService) GetUser(ctx context.Context, id int64) (*user.User, error) {
    return s.repo.Select().
        Where(orm.User.Id.Equals(id)).
        QueryRow(ctx)
}

func (s *UserService) ListUsers(ctx context.Context, req *user.ListUsersRequest) ([]*user.User, error) {
    query := s.repo.Select()

    if req.Search != "" {
        query = query.Where(
            orm.User.Or(
                orm.User.FirstName.Contains(req.Search),
                orm.User.LastName.Contains(req.Search),
                orm.User.Email.Contains(req.Search),
            ),
        )
    }

    if req.MinAge > 0 {
        query = query.Where(orm.User.Age.GreaterThanOrEqual(int32(req.MinAge)))
    }

    if req.MaxAge > 0 {
        query = query.Where(orm.User.Age.LessThanOrEqual(int32(req.MaxAge)))
    }

    if req.ActiveOnly {
        query = query.Where(orm.User.IsActive.IsTrue())
    }

    query = query.
        OrderByASC(orm.User.LastName, orm.User.FirstName).
        Limit(int(req.Limit)).
        Offset(int(req.Offset))

    return query.Query(ctx)
}

func (s *UserService) UpdateUser(ctx context.Context, id int64, req *user.UpdateUserRequest) (*user.User, error) {
    updates := []orm.ValueSetter[orm.UserField]{}

    if req.FirstName != nil {
        updates = append(updates, orm.User.FirstName.Set(*req.FirstName))
    }
    if req.LastName != nil {
        updates = append(updates, orm.User.LastName.Set(*req.LastName))
    }
    if req.Age != nil {
        updates = append(updates, orm.User.Age.Set(int32(*req.Age)))
    }
    if req.IsActive != nil {
        updates = append(updates, orm.User.IsActive.Set(*req.IsActive))
    }

    // Always update updated_at
    updates = append(updates, orm.User.UpdatedAt.Set(time.Now()))

    return s.repo.Update().
        Set(updates...).
        Where(orm.User.Id.Equals(id)).
        ReturningAll().
        QueryRow(ctx)
}

func (s *UserService) DeleteUser(ctx context.Context, id int64) error {
    return s.repo.Delete().
        Where(orm.User.Id.Equals(id)).
        Exec(ctx)
}

πŸ“– API Reference

Interfaces

TableI

Main table interface:

type TableI[F fieldAlias, T targeter[F]] interface {
    AllFields() []F
    AllFieldsExcept(field ...F) []F
    Name() string
    NewScanner() T
    Select(field ...F) *SelectQuery[F]
    Select1() *SelectQuery[F]
    SelectAll() *SelectQuery[F]
    Insert() *InsertQuery[F]
    Update() *UpdateQuery[F]
    Delete() *DeleteQuery[F]
    Query(ctx context.Context, db DB, query ormQuery) ([]T, error)
    QueryRow(ctx context.Context, db DB, query ormQuery) (T, error)
    Execute(ctx context.Context, db DB, query ormQuery) (int64, error)
}

Repository

Repository interface:

type ProtoRepository[F fieldAlias, T targeter[F], P proto.Message] interface {
    Insert(ctx context.Context, entity P, opts ...ProtoCallOption[F, T, P]) error
    Update(ctx context.Context, entity P, opts ...ProtoCallOption[F, T, P]) error
    Delete(ctx context.Context, entity P, opts ...ProtoCallOption[F, T, P]) error
    Select() *SelectQuery[F]
    // ... other methods
}

Operators

CommonOperator

Basic operators for all types:

Equals(value any) Clause[F]
NotEquals(value any) Clause[F]
In(values []any) Clause[F]
NotIn(values []any) Clause[F]
IsNull() Clause[F]
IsNotNull() Clause[F]

ScalarOperator

Operators for scalar types:

GreaterThan(value any) Clause[F]
GreaterThanOrEqual(value any) Clause[F]
LessThan(value any) Clause[F]
LessThanOrEqual(value any) Clause[F]
Between(min, max any) Clause[F]

LikeOperator

Operators for strings:

Contains(value string) Clause[F]
StartsWith(value string) Clause[F]
EndsWith(value string) Clause[F]
Like(pattern string) Clause[F]
ILike(pattern string) Clause[F]

Data Types

SQL types and their PGX equivalents:

Proto Type SQL Type PGX Type
string TEXT string
int32 INTEGER int32
int64 BIGINT int64
float REAL float32
double DOUBLE_PRECISION float64
bool BOOLEAN bool
google.protobuf.Timestamp TIMESTAMPTZ time.Time
repeated T T[] []T
message JSONB pgtype.JSONB

🀝 Contributing

Development

# Clone repository
git clone https://github.com/yaroher/protoc-gen-pgx-orm.git
cd protoc-gen-pgx-orm

# Install dependencies
go mod download

# Run tests
make test

# Generate test code
make build-test

# Build plugin
make build

Testing

# Run all tests
go test ./...

# Run tests with coverage
go test -cover ./...

# Run benchmarks
go test -bench=. ./...

πŸ“„ License

MIT License - see LICENSE file for details.

πŸ”— Links

πŸ†˜ Support

If you have questions or issues:

  1. Check Issues
  2. Create a new Issue with detailed description
  3. Include a minimal reproducible example

protoc-gen-pgx-orm is a powerful tool for automating database operations in Go projects using Protocol Buffers.

About

protoc generator for golang pgx library with type save sql-queries generation and sql schema gen

Resources

Stars

Watchers

Forks

Packages

No packages published