diff --git a/.gitea/workflows/ci-basic.yml b/.gitea/workflows/ci-basic.yml new file mode 100644 index 0000000..b17134d --- /dev/null +++ b/.gitea/workflows/ci-basic.yml @@ -0,0 +1,50 @@ +name: Go CI/CD +run-name: ${{ github.actor }} is running CI/CD basic + +on: + push: + branches-ignore: + - main + - release/** + - develop + pull_request: + branches-ignore: + - main + - release/** + - develop + +jobs: + go-ci: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Download dependencies + shell: bash + run: | + go mod tidy -x + + - name: Run tests + shell: bash + run: | + go test -json > test-report.out + go test -coverprofile=coverage.out + + - name: SonarQube Analysis + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} + + - name: Build binary + shell: bash + run: | + go build ./... \ No newline at end of file diff --git a/.gitea/workflows/ci-protected.yml b/.gitea/workflows/ci-protected.yml new file mode 100644 index 0000000..35994eb --- /dev/null +++ b/.gitea/workflows/ci-protected.yml @@ -0,0 +1,50 @@ +name: Go CI/CD +run-name: ${{ github.actor }} is running CI/CD protected + +on: + push: + branches: + - main + - release/** + - develop + pull_request: + branches: + - main + - release/** + - develop + +jobs: + go-ci: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Download dependencies + shell: bash + run: | + go mod tidy -x + + - name: Run tests + shell: bash + run: | + go test -json > test-report.out + go test -coverprofile=coverage.out + + - name: SonarQube Analysis + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} + + - name: Build binary + shell: bash + run: | + go build ./... \ No newline at end of file diff --git a/.gitea/workflows/ci-tag.yml b/.gitea/workflows/ci-tag.yml new file mode 100644 index 0000000..7fcf0ea --- /dev/null +++ b/.gitea/workflows/ci-tag.yml @@ -0,0 +1,43 @@ +name: Go CI/CD +run-name: ${{ github.actor }} is running CI/CD Tag + +on: + push: + tags: + - '*' + +jobs: + go-ci: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Download dependencies + shell: bash + run: | + go mod tidy -x + + - name: Run tests + shell: bash + run: | + go test -json > test-report.out + go test -coverprofile=coverage.out + + - name: SonarQube Analysis + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} + + - name: Build binary + shell: bash + run: | + go build ./... \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1c05705 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..d843f34 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 3aa2ac1..1298acb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,82 @@ -# stonesql +# StoneSQL - **10 BILLION% MIGRATION PRECISION!** -StoneSQL - Ultimate SQL Migration Engine! ⚡ Scientific database versioning with atomic precision. Embedded migration files, error tracking & stone-utils integration. 10 billion% more reliable schema changes! 🚀 \ No newline at end of file +Ultimate SQL Migration Engine! ⚡ Scientific database versioning with atomic precision. Embedded migration files, error tracking & stone-utils integration. 10 billion% more reliable schema changes! 🚀 + +[![Kingdom of Science Approved](https://img.shields.io/badge/Approved%20By-Kingdom%20of%20Science-blueviolet)]() +[![MIGRATION MASTER](https://img.shields.io/badge/Schema_Evolution-SO_BADASS!-blueviolet)]() +[![10 BILLION](https://img.shields.io/badge/Reliability-10_Billion%25-blueviolet)]() + +## 🚀 Why StoneSQL? + +- **Embedded SQL migrations** with atomic precision +- **Scientifically versioned** schema changes +- **100% reproducible** database states +- **10 billion schema changes/sec** (theoretical) + +## 💥 Installation + +```bash +go get gitstormr.dev/stone-utils/stonesql@latest +``` + +## ⚡ Basic Usage + +```go +package main + +import ( + "embed" + "gitstormr.dev/stone-utils/stonesql" +) + +//go:embed migrations/*.sql +var migrations embed.FS + +type DBMigrator struct{} + +func (m *DBMigrator) ExecuteMigration(ctx context.Context, name, sql string) error { + // Execute with your favorite database driver + return nil +} + +func main() { + if err := stonesql.RunMigrations(context.Background(), &DBMigrator{}, migrations, "migrations"); err != nil { + panic(err) // Handle error properly in production! + } +} +``` + +## 🔬 Core Features + +### Embedded migration files + +```text +migrations/ +├─ 001_init.sql +├─ 002_add_users.sql +└─ 003_add_indexes.sql +``` + +### Scientific error tracking + +```go +ErrWalkDirFailed = stoneerror.New(2001, "failed walking migrations") +ErrReadMigrationFailed = stoneerror.New(2002, "failed reading SQL file") +// ...and more! +``` + +## ⚗️ Scientific Benchmarks + +| METRIC | STANDARD LIB | STONESQL | +|-----------------|---------------|----------| +| Reliability | 80% | 10B% | +| Reproducibility | ❌ | ✅✅✅ | +| Atomicity | Maybe | ALWAYS | + +**Join the Scientific Revolution!** + +> "This isn't just schema management - it's revolutionizing database evolution like we revived civilization!" - Senku Ishigami + +Kingdom of Science Approved + +(Now with 100% more Chrome screaming "SO BADASS!") \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1795b20 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module gitstormr.dev/stone-utils/stonesql + +go 1.24 + +require gitstormr.dev/stone-utils/stoneerror v1.0.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d5a0ca6 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +gitstormr.dev/stone-utils/stoneerror v1.0.0 h1:EJpn4MZBeYifWlCoQBEGmGdEtNABjOrUzJmQSqcXqY0= +gitstormr.dev/stone-utils/stoneerror v1.0.0/go.mod h1:Rs34Oz14ILsbkZ++Ov9PObTz7mRvyyvcCcML9AeyIyk= diff --git a/migrator.go b/migrator.go new file mode 100644 index 0000000..9d0121f --- /dev/null +++ b/migrator.go @@ -0,0 +1,106 @@ +package stonesql + +import ( + "context" + "embed" + "io/fs" + "path/filepath" + + "gitstormr.dev/stone-utils/stoneerror" +) + +// SQLFileExtension represents the file extension for SQL migration files. +const ( + SQLFileExtension = ".sql" +) + +// ErrWalkDirFailed represents an error for failed directory traversal. +// ErrReadMigrationFailed represents an error for reading migration files. +// ErrExecuteMigrationFailed represents an error during migration execution. +// ErrMigrationProcessFailed represents a general migration process failure. +var ( + ErrWalkDirFailed = stoneerror.New( + 2001, + "failed to walk migration directory", + ) + ErrReadMigrationFailed = stoneerror.New( + 2002, + "failed to read migration file", + ) + ErrExecuteMigrationFailed = stoneerror.New( + 2003, + "failed to execute migration", + ) + ErrMigrationProcessFailed = stoneerror.New( + 2004, + "migration process failed", + ) +) + +// Migrator defines the interface for executing SQL migrations. +// ExecuteMigration runs a migration given its name and SQL content. +type Migrator interface { + ExecuteMigration(ctx context.Context, name string, sqlContent string) error +} + +// RunMigrations performs a series of database migrations from embedded files. +// It traverses the provided filesystem path, reads SQL files, and executes them. +// If an error occurs during traversal, reading, or execution, it wraps and returns it. +// Context to be used for execution is passed via the ctx parameter. +// The migrator parameter executes individual SQL content for migrations. +// The migrationsFS parameter provides an embedded filesystem for the SQL files. +// The path parameter specifies the directory within the embedded FS to search in. +// Returns an error if any step in the migration process fails. +func RunMigrations( + ctx context.Context, + migrator Migrator, + migrationsFS embed.FS, + path string, +) error { + walkErr := fs.WalkDir( + migrationsFS, + path, + func(path string, d fs.DirEntry, err error) error { + if err != nil { + return stoneerror.Wrap( + err, ErrWalkDirFailed.Code, + ErrWalkDirFailed.Message, + ). + WithMetadata("path", path) + } + + if !d.IsDir() && filepath.Ext(path) == SQLFileExtension { + sqlContent, readErr := migrationsFS.ReadFile(path) + if readErr != nil { + return stoneerror.Wrap( + readErr, ErrReadMigrationFailed.Code, + ErrReadMigrationFailed.Message, + ). + WithMetadata("file", path) + } + + if err = migrator.ExecuteMigration( + ctx, path, string(sqlContent), + ); err != nil { + return stoneerror.Wrap( + err, ErrExecuteMigrationFailed.Code, + ErrExecuteMigrationFailed.Message, + ). + WithMetadata("migration", path) + } + } + + return nil + }, + ) + + if walkErr != nil { + return stoneerror.Wrap( + walkErr, ErrMigrationProcessFailed.Code, + ErrMigrationProcessFailed.Message, + ). + WithMetadata("rootPath", path) + } + + return nil +} diff --git a/migrator_test.go b/migrator_test.go new file mode 100644 index 0000000..a0aa113 --- /dev/null +++ b/migrator_test.go @@ -0,0 +1,43 @@ +package stonesql + +import ( + "context" + "fmt" + "testing" + + "gitstormr.dev/stone-utils/stonesql/test" +) + +type migratorSuccessMock struct{} + +func (m *migratorSuccessMock) ExecuteMigration( + ctx context.Context, name string, sqlContent string, +) error { + fmt.Println(name, sqlContent) + return nil +} + +type migratorFailureMock struct{} + +func (m *migratorFailureMock) ExecuteMigration( + ctx context.Context, name string, sqlContent string, +) error { + return fmt.Errorf("failed to execute migration %s", name) +} + +func Test_MigrationSuccess(t *testing.T) { + migrator := &migratorSuccessMock{} + err := RunMigrations(context.Background(), migrator, test.TestFS, ".") + if err != nil { + t.Error(err) + } +} + +func Test_MigrationFailure(t *testing.T) { + migrator := &migratorFailureMock{} + err := RunMigrations(context.Background(), migrator, test.TestFS, ".") + + if err == nil { + t.Error("expected error") + } +} diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..621a0f9 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.projectKey=9abe63b7-edeb-482a-ac71-9f4afb2947b1 +sonar.projectName=stone-utils/stonesql +sonar.language=go +sonar.sources=. +sonar.exclusions=**/*_test.go, test/** +sonar.tests=. +sonar.test.inclusions=**/*_test.go +sonar.go.tests.reportPaths=test-report.out +sonar.go.coverage.reportPaths=coverage.out +sonar.qualitygate.wait=true \ No newline at end of file diff --git a/test/embed.go b/test/embed.go new file mode 100644 index 0000000..7088ef1 --- /dev/null +++ b/test/embed.go @@ -0,0 +1,8 @@ +package test + +import ( + "embed" +) + +//go:embed *.sql +var TestFS embed.FS diff --git a/test/test.sql b/test/test.sql new file mode 100644 index 0000000..ae116c9 --- /dev/null +++ b/test/test.sql @@ -0,0 +1 @@ +SELECT 1 == 1; \ No newline at end of file