diff --git a/.claude/commands/build.md b/.claude/commands/build.md new file mode 100644 index 0000000..f1dfeb1 --- /dev/null +++ b/.claude/commands/build.md @@ -0,0 +1,5 @@ +--- +description: Build the RelSpec binary +--- + +Build the RelSpec project by running `go build -o relspec ./cmd/relspec`. Report the build status and any errors encountered. diff --git a/.claude/commands/coverage.md b/.claude/commands/coverage.md new file mode 100644 index 0000000..79e11a8 --- /dev/null +++ b/.claude/commands/coverage.md @@ -0,0 +1,9 @@ +--- +description: Generate test coverage report +--- + +Generate and display test coverage for RelSpec: +1. Run `go test -cover ./...` to get coverage percentage +2. If detailed coverage is needed, run `go test -coverprofile=coverage.out ./...` and then `go tool cover -html=coverage.out` to generate HTML report + +Show coverage statistics and identify areas needing more tests. diff --git a/.claude/commands/lint.md b/.claude/commands/lint.md new file mode 100644 index 0000000..0992c98 --- /dev/null +++ b/.claude/commands/lint.md @@ -0,0 +1,10 @@ +--- +description: Run Go linters on the codebase +--- + +Run linting tools on the RelSpec codebase: +1. First run `gofmt -l .` to check formatting +2. If golangci-lint is available, run `golangci-lint run ./...` +3. Run `go vet ./...` to check for suspicious constructs + +Report any issues found and suggest fixes if needed. diff --git a/.claude/commands/test.md b/.claude/commands/test.md new file mode 100644 index 0000000..8375a8e --- /dev/null +++ b/.claude/commands/test.md @@ -0,0 +1,5 @@ +--- +description: Run all tests for the RelSpec project +--- + +Run `go test ./...` to execute all unit tests in the project. Show a summary of the results and highlight any failures. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..cb3f2ca --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,25 @@ +{ + "project": { + "name": "RelSpec", + "description": "Database Relations Specification Tool for Go", + "language": "go" + }, + "agent": { + "preferred": "Explore", + "description": "Use Explore agent for fast codebase navigation and Go project exploration" + }, + "codeStyle": { + "useGofmt": true, + "lineLength": 100, + "tabWidth": 4, + "useTabs": true + }, + "testing": { + "framework": "go test", + "runOnChange": false + }, + "build": { + "command": "go build -o relspec ./cmd/relspec", + "outputBinary": "relspec" + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1880326 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +# Go files +[*.go] +indent_style = tab +indent_size = 4 + +# YAML files +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +# JSON files +[*.json] +indent_style = space +indent_size = 2 + +# Markdown files +[*.md] +trim_trailing_whitespace = false + +# Makefile +[Makefile] +indent_style = tab + +# Shell scripts +[*.sh] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bf7473c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,85 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + go-version: ['1.23', '1.24', '1.25'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download dependencies + run: go mod download + + - name: Run tests + run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.out + flags: unittests + name: codecov-umbrella + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + args: --config=.golangci.json + + build: + name: Build + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Build + run: go build -v ./cmd/relspec + + - name: Check mod tidiness + run: | + go mod tidy + git diff --exit-code go.mod go.sum diff --git a/.gitignore b/.gitignore index adf8f72..e4e10cc 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,29 @@ # Go workspace file go.work +# RelSpec specific +relspec +*.test +coverage.out +coverage.html + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Test outputs +test_output/ +*.db +*.sqlite +*.sqlite3 + +# Build artifacts +dist/ +build/ diff --git a/.golangci.json b/.golangci.json new file mode 100644 index 0000000..436536b --- /dev/null +++ b/.golangci.json @@ -0,0 +1,114 @@ +{ + "formatters": { + "enable": [ + "gofmt", + "goimports" + ], + "exclusions": { + "generated": "lax", + "paths": [ + "third_party$", + "builtin$", + "examples$" + ] + }, + "settings": { + "gofmt": { + "simplify": true + }, + "goimports": { + "local-prefixes": [ + "git.warky.dev/wdevs/relspecgo" + ] + } + } + }, + "issues": { + "max-issues-per-linter": 0, + "max-same-issues": 0 + }, + "linters": { + "enable": [ + "gocritic", + "misspell", + "revive" + ], + "exclusions": { + "generated": "lax", + "paths": [ + "third_party$", + "builtin$", + "examples$", + "mocks?", + "tests?" + ], + "rules": [ + { + "linters": [ + "dupl", + "errcheck", + "gocritic", + "gosec" + ], + "path": "_test\\.go" + }, + { + "linters": [ + "errcheck" + ], + "text": "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked" + }, + { + "path": "_test\\.go", + "text": "cognitive complexity|cyclomatic complexity" + } + ] + }, + "settings": { + "errcheck": { + "check-blank": false, + "check-type-assertions": false + }, + "gocritic": { + "enabled-checks": [ + "boolExprSimplify", + "builtinShadow", + "emptyFallthrough", + "equalFold", + "indexAlloc", + "initClause", + "methodExprCall", + "nilValReturn", + "rangeExprCopy", + "rangeValCopy", + "stringXbytes", + "typeAssertChain", + "unlabelStmt", + "unnamedResult", + "unnecessaryBlock", + "weakCond", + "yodaStyleExpr" + ], + "disabled-checks": [ + "ifElseChain" + ] + }, + "revive": { + "rules": [ + { + "disabled": true, + "name": "exported" + }, + { + "disabled": true, + "name": "package-comments" + } + ] + } + } + }, + "run": { + "tests": true + }, + "version": "2" +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7f0c249 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,145 @@ +# Contributing to RelSpec + +Thank you for your interest in contributing to RelSpec. + +## Development Setup + +### Prerequisites +- Go 1.21 or higher +- Git +- (Optional) golangci-lint for linting +- (Optional) Docker for database testing + +### Getting Started + +1. Clone the repository: +```bash +git clone https://github.com/wdevs/relspecgo.git +cd relspecgo +``` + +2. Install dependencies: +```bash +go mod download +``` + +3. Run tests: +```bash +go test ./... +``` + +4. Build the project: +```bash +go build -o relspec ./cmd/relspec +``` + +## Project Structure + +``` +relspecgo/ +├── cmd/ # CLI application entry point +├── pkg/ +│ ├── readers/ # Input format readers (XML, JSON, DCTX, DB, GORM, Bun) +│ ├── writers/ # Output format writers (GORM, Bun, JSON, YAML) +│ ├── models/ # Internal data models for relations +│ └── transform/ # Transformation and validation logic +├── examples/ # Usage examples and sample files +├── tests/ # Integration tests +└── .claude/ # Claude Code configuration and commands +``` + +## Adding New Readers + +To add a new input format reader: + +1. Create a new file in `pkg/readers/` (e.g., `myformat_reader.go`) +2. Implement the `Reader` interface: +```go +type Reader interface { + Read(source string) (*models.Schema, error) +} +``` +3. Add tests in `pkg/readers/myformat_reader_test.go` +4. Register the reader in the CLI + +## Adding New Writers + +To add a new output format writer: + +1. Create a new file in `pkg/writers/` (e.g., `myformat_writer.go`) +2. Implement the `Writer` interface: +```go +type Writer interface { + Write(schema *models.Schema, destination string) error +} +``` +3. Add tests in `pkg/writers/myformat_writer_test.go` +4. Register the writer in the CLI + +## Code Style + +- Follow standard Go conventions +- Use `gofmt` for formatting +- Run `go vet` to check for issues +- Use meaningful variable and function names +- Add comments for exported functions and types + +## Testing + +- Write unit tests for all new functionality +- Aim for >80% code coverage +- Use table-driven tests where appropriate +- Include both positive and negative test cases + +### Running Tests + +```bash +# All tests +go test ./... + +# With coverage +go test -cover ./... + +# Verbose output +go test -v ./... + +# Specific package +go test ./pkg/readers/... +``` + +## Committing Changes + +- Write clear, descriptive commit messages +- Follow conventional commits format: `type(scope): description` + - Types: feat, fix, docs, test, refactor, chore + - Example: `feat(readers): add PostgreSQL support` +- Keep commits focused and atomic +- Reference issues in commit messages when applicable + +## Pull Request Process + +1. Create a feature branch from `master` +2. Make your changes +3. Add tests for new functionality +4. Ensure all tests pass +5. Update documentation if needed +6. Submit a pull request with a clear description + +## Claude Code Commands + +This project includes Claude Code slash commands for common tasks: + +- `/test` - Run all tests +- `/build` - Build the binary +- `/lint` - Run linters +- `/coverage` - Generate coverage report + +## Questions or Issues? + +- Open an issue for bugs or feature requests +- Start a discussion for questions or ideas +- Check existing issues before creating new ones + +## License + +By contributing to RelSpec, you agree that your contributions will be licensed under the Apache License 2.0. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c4d60c9 --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +.PHONY: all build test lint coverage clean install help + +# Binary name +BINARY_NAME=relspec + +# Build directory +BUILD_DIR=build + +# Go parameters +GOCMD=go +GOBUILD=$(GOCMD) build +GOTEST=$(GOCMD) test +GOGET=$(GOCMD) get +GOMOD=$(GOCMD) mod +GOCLEAN=$(GOCMD) clean + +all: lint test build ## Run linting, tests, and build + +build: ## Build the binary + @echo "Building $(BINARY_NAME)..." + @mkdir -p $(BUILD_DIR) + $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/relspec + @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)" + +test: ## Run tests + @echo "Running tests..." + $(GOTEST) -v -race -coverprofile=coverage.out ./... + +coverage: test ## Run tests with coverage report + @echo "Generating coverage report..." + $(GOCMD) tool cover -html=coverage.out -o coverage.html + @echo "Coverage report generated: coverage.html" + +lint: ## Run linter + @echo "Running linter..." + @if command -v golangci-lint > /dev/null; then \ + golangci-lint run --config=.golangci.json; \ + else \ + echo "golangci-lint not installed. Install with: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"; \ + exit 1; \ + fi + +clean: ## Clean build artifacts + @echo "Cleaning..." + $(GOCLEAN) + rm -rf $(BUILD_DIR) + rm -f coverage.out coverage.html + @echo "Clean complete" + +install: ## Install the binary to $GOPATH/bin + @echo "Installing $(BINARY_NAME)..." + $(GOCMD) install ./cmd/relspec + @echo "Install complete" + +deps: ## Download dependencies + @echo "Downloading dependencies..." + $(GOMOD) download + $(GOMOD) tidy + @echo "Dependencies updated" + +help: ## Display this help screen + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md index 286a3a0..4396a5a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,93 @@ -# relspecgo +# RelSpec -Resolve Spec Go \ No newline at end of file +> Database Relations Specification Tool for Go + +RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs. + +## Overview + +RelSpec provides bidirectional conversion between various database specification formats, allowing you to: +- Inspect live databases and extract their structure +- Convert between different ORM models (GORM, Bun) +- Transform legacy schema definitions (Clarion DCTX, XML, JSON) +- Generate standardized specification files (JSON, YAML) + +## Features + +### Input Formats +- **XML** - Generic XML schema definitions +- **JSON** - JSON-based schema specifications +- **Clarion DCTX** - Clarion database dictionary format +- **Database Inspection** - Direct database introspection +- **GORM Models** - Read existing GORM Go structs +- **Bun Models** - Read existing Bun Go structs + +### Output Formats +- **GORM Models** - Generate GORM-compatible Go structs +- **Bun Models** - Generate Bun-compatible Go structs +- **JSON** - Standard JSON schema output +- **YAML** - Human-readable YAML format + +## Installation + +```bash +go get github.com/wdevs/relspecgo +``` + +## Usage + +```bash +# Inspect database and generate GORM models +relspec --input db --conn "postgres://..." --output gorm --out-file models.go + +# Convert GORM models to Bun +relspec --input gorm --in-file existing.go --output bun --out-file bun_models.go + +# Export database schema to JSON +relspec --input db --conn "mysql://..." --output json --out-file schema.json + +# Convert Clarion DCTX to YAML +relspec --input dctx --in-file legacy.dctx --output yaml --out-file schema.yaml +``` + +## Project Structure + +``` +relspecgo/ +├── cmd/ # CLI application +├── pkg/ +│ ├── readers/ # Input format readers +│ ├── writers/ # Output format writers +│ ├── models/ # Internal data models +│ └── transform/ # Transformation logic +├── examples/ # Usage examples +└── tests/ # Test files +``` + +## Development + +### Prerequisites +- Go 1.21 or higher +- Access to test databases (optional) + +### Building + +```bash +go build -o relspec ./cmd/relspec +``` + +### Testing + +```bash +go test ./... +``` + +## License + +Apache License 2.0 - See [LICENSE](LICENSE) for details. + +Copyright 2025 wdevs + +## Contributing + +Contributions welcome. Please open an issue or submit a pull request. \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..080d835 --- /dev/null +++ b/TODO.md @@ -0,0 +1,120 @@ +# RelSpec - TODO List + +## Project Setup +- [ ] Initialize Go module (`go.mod`) +- [ ] Set up project directory structure +- [ ] Create `.editorconfig` for consistent formatting +- [ ] Add GitHub Actions for CI/CD +- [ ] Set up pre-commit hooks + +## Core Infrastructure +- [ ] Define internal data model for database relations +- [ ] Implement relation types (one-to-one, one-to-many, many-to-many) +- [ ] Create validation framework for specifications +- [ ] Design plugin architecture for readers/writers + +## Input Readers +- [ ] **Database Inspector** + - [ ] PostgreSQL driver + - [ ] MySQL driver + - [ ] SQLite driver + - [ ] MSSQL driver + - [ ] Foreign key detection + - [ ] Index extraction + +- [ ] **XML Reader** + - [ ] XML schema parser + - [ ] Validation against XSD + +- [ ] **JSON Reader** + - [ ] JSON schema parser + - [ ] Schema validation + +- [ ] **Clarion DCTX Reader** + - [ ] DCTX file parser + - [ ] Legacy format support + +- [ ] **GORM Model Reader** + - [ ] Go AST parser for GORM tags + - [ ] Struct field analysis + - [ ] Relation tag extraction + +- [ ] **Bun Model Reader** + - [ ] Go AST parser for Bun tags + - [ ] Struct field analysis + - [ ] Relation tag extraction + +## Output Writers +- [ ] **GORM Writer** + - [ ] Go struct generation + - [ ] GORM tag formatting + - [ ] Relation definitions + - [ ] gofmt integration + +- [ ] **Bun Writer** + - [ ] Go struct generation + - [ ] Bun tag formatting + - [ ] Relation definitions + - [ ] gofmt integration + +- [ ] **JSON Writer** + - [ ] Schema serialization + - [ ] Pretty printing + +- [ ] **YAML Writer** + - [ ] Schema serialization + - [ ] Comment preservation + +## CLI Application +- [ ] Command-line interface using cobra +- [ ] Input format flags +- [ ] Output format flags +- [ ] Connection string handling +- [ ] File I/O operations +- [ ] Progress indicators +- [ ] Error handling and reporting +- [ ] Configuration file support + +## Testing +- [ ] Unit tests for each reader +- [ ] Unit tests for each writer +- [ ] Integration tests for conversion pipelines +- [ ] Test fixtures for all formats +- [ ] Database test containers +- [ ] Benchmark tests for large schemas + +## Documentation +- [ ] API documentation (godoc) +- [ ] Usage examples for each format combination +- [ ] Migration guides +- [ ] Architecture documentation +- [ ] Contributing guidelines + +## Advanced Features +- [ ] Dry-run mode for validation +- [ ] Diff tool for comparing specifications +- [ ] Migration script generation +- [ ] Custom type mapping configuration +- [ ] Batch processing support +- [ ] Watch mode for auto-regeneration + +## Performance +- [ ] Concurrent processing for multiple tables +- [ ] Streaming for large databases +- [ ] Memory optimization +- [ ] Caching layer for repeated operations + +## Quality & Maintenance +- [ ] Linting with golangci-lint +- [ ] Code coverage > 80% +- [ ] Security scanning +- [ ] Dependency updates automation +- [ ] Release automation + +## Future Considerations +- [ ] Web UI for visual editing +- [ ] REST API server mode +- [ ] Support for NoSQL databases +- [ ] GraphQL schema generation +- [ ] Prisma schema support +- [ ] TypeORM support diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8f2601c --- /dev/null +++ b/go.mod @@ -0,0 +1,22 @@ +module git.warky.dev/wdevs/relspecgo + +go 1.25.5 + +require ( + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/spf13/viper v1.21.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.28.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fcb638b --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/make_release.sh b/make_release.sh new file mode 100644 index 0000000..d1b42c5 --- /dev/null +++ b/make_release.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Ask if the user wants to make a release version +read -p "Do you want to make a release version? (y/n): " make_release + +if [[ $make_release =~ ^[Yy]$ ]]; then + # Get the latest tag from git + latest_tag=$(git describe --tags --abbrev=0 2>/dev/null) + + if [ -z "$latest_tag" ]; then + # No tags exist yet, start with v1.0.0 + suggested_version="v1.0.0" + echo "No existing tags found. Starting with $suggested_version" + else + echo "Latest tag: $latest_tag" + + # Remove 'v' prefix if present + version_number="${latest_tag#v}" + + # Split version into major.minor.patch + IFS='.' read -r major minor patch <<< "$version_number" + + # Increment patch version + patch=$((patch + 1)) + + # Construct new version + suggested_version="v${major}.${minor}.${patch}" + echo "Suggested next version: $suggested_version" + fi + + # Ask the user for the version number with the suggested version as default + read -p "Enter the version number (press Enter for $suggested_version): " version + + # Use suggested version if user pressed Enter without input + if [ -z "$version" ]; then + version="$suggested_version" + fi + + # Prepend 'v' to the version if it doesn't start with it + if ! [[ $version =~ ^v ]]; then + version="v$version" + fi + + # Get commit logs since the last tag + if [ -z "$latest_tag" ]; then + # No previous tag, get all commits + commit_logs=$(git log --pretty=format:"- %s" --no-merges) + else + # Get commits since the last tag + commit_logs=$(git log "${latest_tag}..HEAD" --pretty=format:"- %s" --no-merges) + fi + + # Create the tag message + if [ -z "$commit_logs" ]; then + tag_message="Release $version" + else + tag_message="Release $version + +${commit_logs}" + fi + + # Create an annotated tag with the commit logs + git tag -a "$version" -m "$tag_message" + + # Push the tag to the remote repository + git push origin "$version" + + echo "Tag $version created and pushed to the remote repository." +else + echo "No release version created." +fi diff --git a/pkg/models/models.go b/pkg/models/models.go new file mode 100644 index 0000000..6744acd --- /dev/null +++ b/pkg/models/models.go @@ -0,0 +1,149 @@ +package models + +type DatabaseType string + +const ( + PostgresqlDatabaseType DatabaseType = "pgsql" + MSSQLDatabaseType DatabaseType = "mssql" + SqlLiteDatabaseType DatabaseType = "sqlite" +) + +// Database represents the complete database schema +type Database struct { + Name string `json:"name" yaml:"name"` + Schemas []*Schema `json:"schemas" yaml:"schemas" xml:"schemas"` + Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` + DatabaseType DatabaseType `json:"database_type,omitempty" yaml:"database_type,omitempty" xml:"database_type,omitempty"` + DatabaseVersion string `json:"database_version,omitempty" yaml:"database_version,omitempty" xml:"database_version,omitempty"` +} + +type Schema struct { + Name string `json:"name" yaml:"name" xml:"name"` + Tables []*Table `json:"tables" yaml:"tables" xml:"-"` + Owner string `json:"owner" yaml:"owner" xml:"owner"` + Permissions map[string]string `json:"permissions,omitempty" yaml:"permissions,omitempty" xml:"-"` + Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty" xml:"-"` + Scripts []*Script `json:"scripts,omitempty" yaml:"scripts,omitempty" xml:"scripts,omitempty"` +} + +type Table struct { + Name string `json:"name" yaml:"name" xml:"name"` + Schema string `json:"schema" yaml:"schema" xml:"schema"` + Columns map[string]*Column `json:"columns" yaml:"columns" xml:"-"` + Constraints map[string]*Constraint `json:"constraints" yaml:"constraints" xml:"-"` + Indexes map[string]*Index `json:"indexes,omitempty" yaml:"indexes,omitempty" xml:"-"` + Relationships map[string]*Relationship `json:"relationships,omitempty" yaml:"relationships,omitempty" xml:"-"` + Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` + Tablespace string `json:"tablespace,omitempty" yaml:"tablespace,omitempty" xml:"tablespace,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty" xml:"-"` +} + +func (m Table) GetPrimaryKey() *Column { + for _, column := range m.Columns { + if column.IsPrimaryKey { + return column + } + } + return nil +} + +func (m Table) GetForeignKeys() []*Constraint { + keys := make([]*Constraint, 0) + + for _, c := range m.Constraints { + if c.Type == ForeignKeyConstraint { + keys = append(keys, c) + } + } + return keys +} + +// Column represents a table column +type Column struct { + Name string `json:"name" yaml:"name" xml:"name"` + Table string `json:"table" yaml:"table" xml:"table"` + Schema string `json:"schema" yaml:"schema" xml:"schema"` + Type string `json:"type" yaml:"type" xml:"type"` + Length int `json:"length,omitempty" yaml:"length,omitempty" xml:"length,omitempty"` + Precision int `json:"precision,omitempty" yaml:"precision,omitempty" xml:"precision,omitempty"` + Scale int `json:"scale,omitempty" yaml:"scale,omitempty" xml:"scale,omitempty"` + NotNull bool `json:"not_null" yaml:"not_null" xml:"not_null"` + Default interface{} `json:"default,omitempty" yaml:"default,omitempty" xml:"default,omitempty"` + AutoIncrement bool `json:"auto_increment" yaml:"auto_increment" xml:"auto_increment"` + IsPrimaryKey bool `json:"is_primary_key" yaml:"is_primary_key" xml:"is_primary_key"` + Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` + Collation string `json:"collation,omitempty" yaml:"collation,omitempty" xml:"collation,omitempty"` +} + +type Index struct { + Name string `json:"name" yaml:"name" xml:"name"` + Table string `json:"table,omitempty" yaml:"table,omitempty" xml:"table,omitempty"` + Schema string `json:"schema,omitempty" yaml:"schema,omitempty" xml:"schema,omitempty"` + Columns []string `json:"columns" yaml:"columns" xml:"columns"` + Unique bool `json:"unique" yaml:"unique" xml:"unique"` + Type string `json:"type" yaml:"type" xml:"type"` // btree, hash, gin, gist, etc. + Where string `json:"where,omitempty" yaml:"where,omitempty" xml:"where,omitempty"` // partial index condition + Concurrent bool `json:"concurrent,omitempty" yaml:"concurrent,omitempty" xml:"concurrent,omitempty"` + Include []string `json:"include,omitempty" yaml:"include,omitempty" xml:"include,omitempty"` // INCLUDE columns + Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` +} + +type RelationType string + +const ( + OneToOne RelationType = "one_to_one" + OneToMany RelationType = "one_to_many" + ManyToMany RelationType = "many_to_many" +) + +type Relationship struct { + Name string `json:"name" yaml:"name" xml:"name"` + Type RelationType `json:"type" yaml:"type" xml:"type"` + FromTable string `json:"from_table" yaml:"from_table" xml:"from_table"` + FromSchema string `json:"from_schema" yaml:"from_schema" xml:"from_schema"` + ToTable string `json:"to_table" yaml:"to_table" xml:"to_table"` + ToSchema string `json:"to_schema" yaml:"to_schema" xml:"to_schema"` + ForeignKey string `json:"foreign_key" yaml:"foreign_key" xml:"foreign_key"` + Properties map[string]string `json:"properties" yaml:"properties" xml:"-"` + ThroughTable string `json:"through_table,omitempty" yaml:"through_table,omitempty" xml:"through_table,omitempty"` // For many-to-many + ThroughSchema string `json:"through_schema,omitempty" yaml:"through_schema,omitempty" xml:"through_schema,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty" xml:"description,omitempty"` +} + +type Constraint struct { + Name string `json:"name" yaml:"name" xml:"name"` + Type ConstraintType `json:"type" yaml:"type" xml:"type"` + Columns []string `json:"columns" yaml:"columns" xml:"columns"` + Expression string `json:"expression,omitempty" yaml:"expression,omitempty" xml:"expression,omitempty"` + Schema string `json:"schema,omitempty" yaml:"schema,omitempty" xml:"schema,omitempty"` + Table string `json:"table,omitempty" yaml:"table,omitempty" xml:"table,omitempty"` + ReferencedTable string `json:"referenced_table" yaml:"referenced_table" xml:"referenced_table"` + ReferencedSchema string `json:"referenced_schema" yaml:"referenced_schema" xml:"referenced_schema"` + ReferencedColumns []string `json:"referenced_columns" yaml:"referenced_columns" xml:"referenced_columns"` + OnDelete string `json:"on_delete" yaml:"on_delete" xml:"on_delete"` // CASCADE, SET NULL, RESTRICT, etc. + OnUpdate string `json:"on_update" yaml:"on_update" xml:"on_update"` + Deferrable bool `json:"deferrable,omitempty" yaml:"deferrable,omitempty" xml:"deferrable,omitempty"` + InitiallyDeferred bool `json:"initially_deferred,omitempty" yaml:"initially_deferred,omitempty" xml:"initially_deferred,omitempty"` +} + +type ConstraintType string + +const ( + PrimaryKeyConstraint ConstraintType = "primary_key" + ForeignKeyConstraint ConstraintType = "foreign_Key" + UniqueConstraint ConstraintType = "unique" + CheckConstraint ConstraintType = "check" + NotNullConstraint ConstraintType = "not_null" +) + +type Script struct { + Name string `json:"name" yaml:"name" xml:"name"` + Description string `json:"description" yaml:"description" xml:"description"` + SQL string `json:"sql" yaml:"sql" xml:"sql"` + Rollback string `json:"rollback,omitempty" yaml:"rollback,omitempty" xml:"rollback,omitempty"` + RunAfter []string `json:"run_after,omitempty" yaml:"run_after,omitempty" xml:"run_after,omitempty"` + Schema string `json:"schema,omitempty" yaml:"schema,omitempty" xml:"schema,omitempty"` + Version string `json:"version,omitempty" yaml:"version,omitempty" xml:"version,omitempty"` + Priority int `json:"priority,omitempty" yaml:"priority,omitempty" xml:"priority,omitempty"` +} diff --git a/pkg/readers/reader.go b/pkg/readers/reader.go new file mode 100644 index 0000000..e2147b2 --- /dev/null +++ b/pkg/readers/reader.go @@ -0,0 +1,24 @@ +package readers + +import ( + "git.warky.dev/wdevs/relspecgo/pkg/models" +) + +// Reader defines the interface for reading database specifications +// from various input formats +type Reader interface { + // Read reads and parses the input, returning a Database model + Read() (*models.Database, error) +} + +// ReaderOptions contains common options for readers +type ReaderOptions struct { + // FilePath is the path to the input file (if applicable) + FilePath string + + // ConnectionString is the database connection string (for DB readers) + ConnectionString string + + // Additional options can be added here as needed + Metadata map[string]interface{} +} diff --git a/pkg/transform/transform.go b/pkg/transform/transform.go new file mode 100644 index 0000000..9b65d21 --- /dev/null +++ b/pkg/transform/transform.go @@ -0,0 +1,32 @@ +package transform + +import ( + "git.warky.dev/wdevs/relspecgo/pkg/models" +) + +// Transformer provides utilities for transforming database models +type Transformer struct{} + +// NewTransformer creates a new Transformer instance +func NewTransformer() *Transformer { + return &Transformer{} +} + +// Validate validates a database model for correctness +func (t *Transformer) Validate(db *models.Database) error { + // TODO: Implement validation logic + // - Check for duplicate table names + // - Validate column types + // - Ensure foreign keys reference existing tables/columns + // - Validate relation integrity + return nil +} + +// Normalize normalizes a database model to a standard format +func (t *Transformer) Normalize(db *models.Database) (*models.Database, error) { + // TODO: Implement normalization logic + // - Standardize naming conventions + // - Order tables/columns consistently + // - Apply default values + return db, nil +} diff --git a/pkg/writers/writer.go b/pkg/writers/writer.go new file mode 100644 index 0000000..e581183 --- /dev/null +++ b/pkg/writers/writer.go @@ -0,0 +1,24 @@ +package writers + +import ( + "git.warky.dev/wdevs/relspecgo/pkg/models" +) + +// Writer defines the interface for writing database specifications +// to various output formats +type Writer interface { + // Write takes a Database model and writes it to the desired format + Write(db *models.Database) error +} + +// WriterOptions contains common options for writers +type WriterOptions struct { + // OutputPath is the path where the output should be written + OutputPath string + + // PackageName is the Go package name (for code generation) + PackageName string + + // Additional options can be added here as needed + Metadata map[string]interface{} +}