This commit is contained in:
Björn Benouarets
2025-11-05 14:54:58 +01:00
commit f2faff5228
6 changed files with 253 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
example/
example.json

110
README.md Normal file
View File

@@ -0,0 +1,110 @@
# psson - PostgreSQL Schema Generator
Generate a PostgreSQL schema from a defined JSON schema.
⭐️ Only supports PostgreSQL 16+
**Developed by [SecNex](https://secnex.io) and [Björn Benouarets](https://github.com/bbenouarets)**
## Features
✅ Primary key
✅ Unique
✅ Not null
✅ Default
✅ References
✅ Comment
✅ Algorithm
## Data Types
✅ VARCHAR
✅ INTEGER
✅ FLOAT
✅ BOOLEAN
✅ DATE
✅ TIME
✅ TIMESTAMP
✅ UUID
✅ JSONB
## Example
```json
{
"table": "users",
"schema": [
{
"name": "id",
"type": "uuid",
"primary": true,
"comment": "The unique identifier for the user"
},
{
"name": "email",
"type": "string",
"nullable": false,
"unique": true,
"comment": "The email address for the user"
},
{
"name": "password",
"type": "hash",
"nullable": false,
"comment": "The password for the user",
"algorithm": "argon2"
},
{
"name": "organization_id",
"type": "uuid",
"nullable": false,
"comment": "The organization ID for the user",
"references": {
"table": "organizations",
"column": "id",
"onDelete": "CASCADE",
"onUpdate": "CASCADE"
}
}
]
}
```
## Output
```sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE
)
```
## Installation
```bash
go install git.secnex.io/secnex/pgson
```
## Usage
```go
package main
import (
"git.secnex.io/secnex/pgson"
)
func main() {
schema, err := pgson.NewSchemaFromFile("schema.json")
if err != nil {
log.Fatalf("Failed to load schema: %v", err)
}
createSQL, err := schema.CreateSQL()
if err != nil {
log.Fatalf("Failed to create SQL: %v", err)
}
fmt.Println(createSQL)
}
```

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module git.secnex.io/secnex/pgson
go 1.25.3

19
pgson.go Normal file
View File

@@ -0,0 +1,19 @@
package pgson
import (
"os"
"git.secnex.io/secnex/pgson/schema"
)
func NewSchemaFromFile(path string) (*schema.Table, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return schema.NewTable(data)
}
func NewSchemaFromJSON(json []byte) (*schema.Table, error) {
return schema.NewTable(json)
}

9
schema/check.go Normal file
View File

@@ -0,0 +1,9 @@
package schema
var (
requiredAttributes = [2]string{"name", "type"}
)
func (t *Table) Validate() error {
return nil
}

110
schema/table.go Normal file
View File

@@ -0,0 +1,110 @@
package schema
import (
"encoding/json"
"fmt"
"strings"
)
type Table struct {
Name string `json:"table"`
Schema []Field `json:"schema"`
}
type Field struct {
Name string `json:"name"`
Type string `json:"type"`
Nullable *bool `json:"nullable"`
Default *string `json:"default"`
Unique *bool `json:"unique"`
Primary *bool `json:"primary"`
Comment *string `json:"comment"`
References *Reference `json:"references"`
Algorithm *string `json:"algorithm"`
}
type Reference struct {
Table string `json:"table"`
Column string `json:"column"`
OnDelete string `json:"onDelete"`
OnUpdate string `json:"onUpdate"`
}
// Mapping of field types to SQL types
var fieldTypeToSQLType = map[string]string{
"string": "VARCHAR",
"int": "INTEGER",
"float": "FLOAT",
"bool": "BOOLEAN",
"date": "DATE",
"time": "TIME",
"datetime": "TIMESTAMP",
"uuid": "UUID",
"json": "JSONB",
"hash": "VARCHAR",
}
func NewTable(data []byte) (*Table, error) {
var table Table
err := json.Unmarshal(data, &table)
if err != nil {
return nil, err
}
return &table, nil
}
func (t *Table) JSON() ([]byte, error) {
return json.Marshal(t)
}
func (f *Field) SQL() string {
sqlType := fieldTypeToSQLType[f.Type]
if sqlType == "" {
return ""
}
sql := fmt.Sprintf("%s %s", f.Name, sqlType)
if f.Nullable != nil && !*f.Nullable {
sql += " NOT NULL"
}
if f.Primary != nil && *f.Primary {
sql += " PRIMARY KEY"
if f.Default == nil && f.Type == "uuid" {
sql += " DEFAULT uuid_generate_v4()"
}
}
if f.Unique != nil && *f.Unique {
sql += " UNIQUE"
}
if f.Default != nil && f.Primary == nil {
sql += fmt.Sprintf(" DEFAULT %s", *f.Default)
}
if f.References != nil {
sql += fmt.Sprintf(" REFERENCES %s(%s)", f.References.Table, f.References.Column)
if f.References.OnDelete != "" {
sql += fmt.Sprintf(" ON DELETE %s", f.References.OnDelete)
}
if f.References.OnUpdate != "" {
sql += fmt.Sprintf(" ON UPDATE %s", f.References.OnUpdate)
}
}
return sql
}
func (f *Field) SQLReferences() string {
if f.References == nil {
return ""
}
return fmt.Sprintf(" REFERENCES %s(%s)", f.References.Table, f.References.Column)
}
func (t *Table) CreateSQL() (string, error) {
if err := t.Validate(); err != nil {
return "", err
}
schemaParts := make([]string, len(t.Schema))
for i, field := range t.Schema {
schemaParts[i] = field.SQL()
}
query := fmt.Sprintf("CREATE TABLE %s (%s)", t.Name, strings.Join(schemaParts, ",\n"))
return query, nil
}