Init
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
example/
|
||||||
|
example.json
|
||||||
110
README.md
Normal file
110
README.md
Normal 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)
|
||||||
|
}
|
||||||
|
```
|
||||||
19
pgson.go
Normal file
19
pgson.go
Normal 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
9
schema/check.go
Normal 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
110
schema/table.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user