From 09d6218bca0f92f31a68f183c618895990f5d325 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 08:25:30 +0530 Subject: [PATCH 01/19] fix: handle unexpected event types in publish workflow and update test-build triggers --- .github/workflows/publish.yml | 5 ++++- .github/workflows/test-build.yml | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9f15cc3..f8a9a57 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -51,8 +51,11 @@ jobs: if [ "${{ github.event_name }}" = "push" ]; then REF="${{ github.ref }}" TAG_VERSION="${REF#refs/tags/}" - else + elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then TAG_VERSION="${{ inputs.tag }}" + else + echo "❌ Unexpected event type: ${{ github.event_name }}" + exit 1 fi VERSION=$(echo "$TAG_VERSION" | sed 's/^v//') diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index aa2f1f9..ece0b93 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -3,6 +3,8 @@ name: Build & Test on: push: branches: [ trunk, develop ] + tags: + - 'v*' paths-ignore: - '**.md' - 'docs/**' From 3990370f94988807ff8b78d5c4721bd329322da3 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 08:42:49 +0530 Subject: [PATCH 02/19] chore: rename homebrew tap from fakestack to packages - Update workflow to use homebrew-packages repository - Update all documentation with new tap name - Users can now: brew tap 0xdps/packages && brew install fakestack - Prepares tap for future tools beyond fakestack --- .github/workflows/publish.yml | 11 ++++++----- CHANGELOG.md | 3 ++- README.md | 2 +- docs/api-reference.md | 3 ++- docs/getting-started.md | 3 ++- docs/index.md | 3 ++- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f8a9a57..5a2c693 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -412,7 +412,8 @@ jobs: ### Homebrew (macOS/Linux) ```bash - brew install 0xdps/tap/fakestack + brew tap 0xdps/packages + brew install fakestack ``` ### Go @@ -473,9 +474,9 @@ jobs: echo "linux_arm64=$(sha256sum binaries/fakestack-linux-arm64 | awk '{print $1}')" >> $GITHUB_OUTPUT echo "linux_amd64=$(sha256sum binaries/fakestack-linux-amd64 | awk '{print $1}')" >> $GITHUB_OUTPUT - - name: Clone homebrew-fakestack tap + - name: Clone homebrew-packages tap run: | - git clone https://github.com/0xdps/homebrew-fakestack.git tap + git clone https://github.com/0xdps/homebrew-packages.git tap - name: Update Homebrew formula run: | @@ -498,11 +499,11 @@ jobs: cat tap/Formula/fakestack.rb - - name: Push to homebrew-fakestack tap + - name: Push to homebrew-packages tap working-directory: tap run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add Formula/fakestack.rb git commit -m "chore: update formula to ${GITHUB_REF#refs/tags/}" - git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/0xdps/homebrew-fakestack.git main || echo "⚠️ Formula update failed, manual update needed" + git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/0xdps/homebrew-packages.git trunk || echo "⚠️ Formula update failed, manual update needed" diff --git a/CHANGELOG.md b/CHANGELOG.md index 738ad93..510a424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -180,7 +180,8 @@ npm install fakestack **Homebrew:** ```bash -brew install 0xdps/tap/fakestack +brew tap 0xdps/packages +brew install fakestack ``` ### Quick Start diff --git a/README.md b/README.md index 744e0a0..63afa04 100644 --- a/README.md +++ b/README.md @@ -393,7 +393,7 @@ Built with: - 💬 **Discussions**: [GitHub Discussions](https://github.com/0xdps/fake-stack/discussions) - 📦 **PyPI**: https://pypi.org/project/fakestack/ - 📦 **npm**: https://www.npmjs.com/package/fakestack -- 🍺 **Homebrew**: `brew install 0xdps/fakestack` +- 🍺 **Homebrew**: `brew tap 0xdps/packages && brew install fakestack` ## 🔖 Changelog diff --git a/docs/api-reference.md b/docs/api-reference.md index 7bb9a0c..2577970 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -418,7 +418,8 @@ After installing via Homebrew: ```bash # Install -brew install 0xdps/fakestack +brew tap 0xdps/packages +brew install fakestack # Use fakestack schema.json diff --git a/docs/getting-started.md b/docs/getting-started.md index e9137c6..3e123a7 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -18,7 +18,8 @@ npm install fakestack ### Homebrew (macOS/Linux) ```bash -brew install 0xdps/fakestack +brew tap 0xdps/packages +brew install fakestack ``` ### Direct Binary Download diff --git a/docs/index.md b/docs/index.md index 9efab5c..fa467c3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -48,7 +48,8 @@ hide: --- ```bash - brew install 0xdps/fakestack + brew tap 0xdps/packages + brew install fakestack ``` [:octicons-arrow-right-24: Get started](getting-started.md) From 91ef518e3f2ae264c27ebe80271b87b30b7ac2a4 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 09:14:55 +0530 Subject: [PATCH 03/19] feat: add uninstall script for removing fakestack from npm, pip, and Homebrew --- scripts/uninstall.sh | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100755 scripts/uninstall.sh diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 0000000..2860fa3 --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# Fakestack Uninstall Script +# Removes fakestack from npm, pip, and Homebrew + +set -e + +echo "🗑️ Fakestack Uninstall Script" +echo "==============================" +echo "" + +# Color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Track if anything was uninstalled +UNINSTALLED=false + +# Uninstall from npm (global) +echo "📦 Checking npm (global)..." +if npm list -g fakestack 2>/dev/null | grep -q fakestack; then + echo -e "${YELLOW}Uninstalling fakestack from npm...${NC}" + npm uninstall -g fakestack + echo -e "${GREEN}✓ Uninstalled from npm${NC}" + UNINSTALLED=true +else + echo " Not installed globally in npm" +fi +echo "" + +# Uninstall from pip (current environment) +echo "🐍 Checking pip (current environment)..." +if pip show fakestack &>/dev/null; then + echo -e "${YELLOW}Uninstalling fakestack from pip...${NC}" + pip uninstall -y fakestack + echo -e "${GREEN}✓ Uninstalled from pip${NC}" + UNINSTALLED=true +else + echo " Not installed in current pip environment" +fi +echo "" + +# Uninstall from Homebrew +echo "🍺 Checking Homebrew..." +if brew list fakestack &>/dev/null; then + echo -e "${YELLOW}Uninstalling fakestack from Homebrew...${NC}" + brew uninstall fakestack + echo -e "${GREEN}✓ Uninstalled from Homebrew${NC}" + UNINSTALLED=true +else + echo " Not installed in Homebrew" +fi +echo "" + +# Optional: Remove tap +if brew tap | grep -q "0xdps/packages"; then + echo "🔧 Homebrew tap 0xdps/packages is still installed" + read -p "Do you want to remove the tap? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + brew untap 0xdps/packages + echo -e "${GREEN}✓ Removed tap 0xdps/packages${NC}" + fi + echo "" +fi + +# Check pyenv for other Python versions +echo "🔍 Checking other Python environments..." +if command -v pyenv &>/dev/null; then + PYTHON_VERSIONS=$(pyenv versions --bare 2>/dev/null || echo "") + if [ -n "$PYTHON_VERSIONS" ]; then + echo " Found pyenv Python versions. Checking each..." + while IFS= read -r version; do + if pyenv shell "$version" 2>/dev/null && pip show fakestack &>/dev/null; then + echo -e " ${YELLOW}Found in Python $version${NC}" + read -p " Uninstall from Python $version? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + pip uninstall -y fakestack + echo -e " ${GREEN}✓ Uninstalled from Python $version${NC}" + UNINSTALLED=true + fi + fi + done <<< "$PYTHON_VERSIONS" + pyenv shell --unset 2>/dev/null || true + fi +fi +echo "" + +# Summary +echo "==============================" +if [ "$UNINSTALLED" = true ]; then + echo -e "${GREEN}✓ Uninstall complete!${NC}" +else + echo -e "${YELLOW}No installations found${NC}" +fi +echo "" From d82b8ca75e6e94243eae89e6d3ee5e321ca3fc3e Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 09:19:10 +0530 Subject: [PATCH 04/19] feat: update documentation configuration to include mike for deployment --- docs/.readthedocs.yaml | 3 +++ docs/mkdocs.yml | 3 +++ docs/requirements.txt | 1 + 3 files changed, 7 insertions(+) diff --git a/docs/.readthedocs.yaml b/docs/.readthedocs.yaml index af9f463..d410071 100644 --- a/docs/.readthedocs.yaml +++ b/docs/.readthedocs.yaml @@ -7,6 +7,9 @@ build: os: ubuntu-22.04 tools: python: "3.11" + commands: + - pip install mike + - mike deploy --push --update-aliases $READTHEDOCS_VERSION latest python: install: diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f566f15..1858ab6 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -91,6 +91,9 @@ plugins: - offline extra: + version: + provider: mike + default: latest social: - icon: fontawesome/brands/github link: https://github.com/0xdps/fake-stack diff --git a/docs/requirements.txt b/docs/requirements.txt index b6d5aa6..afbf304 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ mkdocs>=1.5.0 mkdocs-material[imaging]>=9.4.0 pymdown-extensions>=10.3 +mike>=2.0.0 From b3d2eacc5393fe2bff7cee1b9143df6848c2fab6 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 09:28:27 +0530 Subject: [PATCH 05/19] feat: add support for MariaDB, MS SQL Server, and CockroachDB - Add MSSQL, MariaDB, and CockroachDB database type constants - Add Microsoft SQL Server driver (github.com/denisenkom/go-mssqldb) - Implement connection strings for new databases - Add MSSQL IDENTITY autoincrement syntax - Add MSSQL NEWID() for random value selection - MariaDB uses MySQL driver (drop-in compatible) - CockroachDB uses PostgreSQL driver - Create manual test workflow for all 6 databases (workflow_dispatch) - Update documentation with new database configurations - Add Docker setup examples for MSSQL and CockroachDB Total supported databases: SQLite, MySQL, PostgreSQL, MariaDB, MSSQL, CockroachDB --- .github/workflows/test-databases.yml | 418 +++++++++++++++++++++++++++ docs/databases.md | 199 ++++++++++++- golang/database.go | 24 +- golang/go.mod | 8 +- golang/go.sum | 38 +++ golang/schema.go | 9 +- 6 files changed, 683 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/test-databases.yml diff --git a/.github/workflows/test-databases.yml b/.github/workflows/test-databases.yml new file mode 100644 index 0000000..8fa4ff1 --- /dev/null +++ b/.github/workflows/test-databases.yml @@ -0,0 +1,418 @@ +name: Test Database Support + +# Manual trigger only - run on demand to test all database integrations +on: + workflow_dispatch: + inputs: + test_all: + description: 'Test all databases' + required: true + default: true + type: boolean + +jobs: + test-mysql: + name: Test MySQL + runs-on: ubuntu-latest + if: github.event.inputs.test_all == 'true' + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: testpass + MYSQL_DATABASE: testdb + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache-dependency-path: golang/go.sum + + - name: Build fakestack + working-directory: golang + run: | + CGO_ENABLED=1 go build -o ../bin/fakestack-test + chmod +x ../bin/fakestack-test + + - name: Create test schema + run: | + cat > test-schema.json << 'EOF' + { + "database": { + "dbtype": "mysql", + "username": "root", + "password": "testpass", + "host": "127.0.0.1:3306", + "database": "testdb" + }, + "tables": [ + { + "name": "users", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "users", + "count": 10, + "fields": [ + {"name": "username", "generator": "user_name"}, + {"name": "email", "generator": "email"} + ] + } + ] + } + EOF + + - name: Test create and populate + run: | + ./bin/fakestack-test -c -f test-schema.json + ./bin/fakestack-test -p -f test-schema.json + + - name: Verify data + run: | + sudo apt-get update && sudo apt-get install -y mysql-client + COUNT=$(mysql -h 127.0.0.1 -u root -ptestpass testdb -N -s -e "SELECT COUNT(*) FROM users;") + echo "Found $COUNT rows" + [ "$COUNT" -eq 10 ] && echo "✅ MySQL test passed" + + test-mariadb: + name: Test MariaDB + runs-on: ubuntu-latest + if: github.event.inputs.test_all == 'true' + + services: + mariadb: + image: mariadb:11 + env: + MYSQL_ROOT_PASSWORD: testpass + MYSQL_DATABASE: testdb + ports: + - 3306:3306 + options: >- + --health-cmd="healthcheck.sh --connect --innodb_initialized" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache-dependency-path: golang/go.sum + + - name: Build fakestack + working-directory: golang + run: | + CGO_ENABLED=1 go build -o ../bin/fakestack-test + chmod +x ../bin/fakestack-test + + - name: Create test schema + run: | + cat > test-schema.json << 'EOF' + { + "database": { + "dbtype": "mariadb", + "username": "root", + "password": "testpass", + "host": "127.0.0.1:3306", + "database": "testdb" + }, + "tables": [ + { + "name": "users", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "users", + "count": 10, + "fields": [ + {"name": "username", "generator": "user_name"}, + {"name": "email", "generator": "email"} + ] + } + ] + } + EOF + + - name: Test create and populate + run: | + ./bin/fakestack-test -c -f test-schema.json + ./bin/fakestack-test -p -f test-schema.json + + - name: Verify data + run: | + sudo apt-get update && sudo apt-get install -y mysql-client + COUNT=$(mysql -h 127.0.0.1 -u root -ptestpass testdb -N -s -e "SELECT COUNT(*) FROM users;") + echo "Found $COUNT rows" + [ "$COUNT" -eq 10 ] && echo "✅ MariaDB test passed" + + test-postgres: + name: Test PostgreSQL + runs-on: ubuntu-latest + if: github.event.inputs.test_all == 'true' + + services: + postgres: + image: postgres:16 + env: + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache-dependency-path: golang/go.sum + + - name: Build fakestack + working-directory: golang + run: | + CGO_ENABLED=1 go build -o ../bin/fakestack-test + chmod +x ../bin/fakestack-test + + - name: Create test schema + run: | + cat > test-schema.json << 'EOF' + { + "database": { + "dbtype": "psql", + "username": "postgres", + "password": "testpass", + "host": "localhost:5432", + "database": "testdb" + }, + "tables": [ + { + "name": "users", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "users", + "count": 10, + "fields": [ + {"name": "username", "generator": "user_name"}, + {"name": "email", "generator": "email"} + ] + } + ] + } + EOF + + - name: Test create and populate + run: | + ./bin/fakestack-test -c -f test-schema.json + ./bin/fakestack-test -p -f test-schema.json + + - name: Verify data + run: | + sudo apt-get update && sudo apt-get install -y postgresql-client + COUNT=$(PGPASSWORD=testpass psql -h localhost -U postgres testdb -t -c "SELECT COUNT(*) FROM users;" | xargs) + echo "Found $COUNT rows" + [ "$COUNT" -eq 10 ] && echo "✅ PostgreSQL test passed" + + test-sqlite: + name: Test SQLite + runs-on: ubuntu-latest + if: github.event.inputs.test_all == 'true' + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache-dependency-path: golang/go.sum + + - name: Build fakestack + working-directory: golang + run: | + CGO_ENABLED=1 go build -o ../bin/fakestack-test + chmod +x ../bin/fakestack-test + + - name: Create test schema + run: | + cat > test-schema.json << 'EOF' + { + "database": { + "dbtype": "sqlite", + "database": "test.db" + }, + "tables": [ + { + "name": "users", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "users", + "count": 10, + "fields": [ + {"name": "username", "generator": "user_name"}, + {"name": "email", "generator": "email"} + ] + } + ] + } + EOF + + - name: Test create and populate + run: | + ./bin/fakestack-test -c -f test-schema.json + ./bin/fakestack-test -p -f test-schema.json + + - name: Verify data + run: | + sudo apt-get update && sudo apt-get install -y sqlite3 + COUNT=$(sqlite3 test.db "SELECT COUNT(*) FROM users;") + echo "Found $COUNT rows" + [ "$COUNT" -eq 10 ] && echo "✅ SQLite test passed" + + test-mssql: + name: Test MS SQL Server + runs-on: ubuntu-latest + if: github.event.inputs.test_all == 'true' + + services: + mssql: + image: mcr.microsoft.com/mssql/server:2022-latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: TestPass123! + MSSQL_PID: Developer + ports: + - 1433:1433 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache-dependency-path: golang/go.sum + + - name: Wait for MSSQL + run: sleep 30 + + - name: Setup database + run: | + docker exec $(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) \ + /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' \ + -Q "CREATE DATABASE testdb;" + + - name: Build fakestack + working-directory: golang + run: | + CGO_ENABLED=1 go build -o ../bin/fakestack-test + chmod +x ../bin/fakestack-test + + - name: Create test schema + run: | + cat > test-schema.json << 'EOF' + { + "database": { + "dbtype": "mssql", + "username": "sa", + "password": "TestPass123!", + "host": "localhost:1433", + "database": "testdb" + }, + "tables": [ + { + "name": "users", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "users", + "count": 10, + "fields": [ + {"name": "username", "generator": "user_name"}, + {"name": "email", "generator": "email"} + ] + } + ] + } + EOF + + - name: Test create and populate + run: | + ./bin/fakestack-test -c -f test-schema.json + ./bin/fakestack-test -p -f test-schema.json + + - name: Verify data + run: | + COUNT=$(docker exec $(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) \ + /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' -d testdb -h -1 \ + -Q "SET NOCOUNT ON; SELECT COUNT(*) FROM users;" | xargs) + echo "Found $COUNT rows" + [ "$COUNT" -eq 10 ] && echo "✅ MSSQL test passed" + + summary: + name: Test Summary + needs: [test-mysql, test-mariadb, test-postgres, test-sqlite, test-mssql] + runs-on: ubuntu-latest + if: always() + + steps: + - name: Summary + run: | + echo "🎯 Database Testing Complete" + echo "==============================" + echo "MySQL: ${{ needs.test-mysql.result }}" + echo "MariaDB: ${{ needs.test-mariadb.result }}" + echo "PostgreSQL: ${{ needs.test-postgres.result }}" + echo "SQLite: ${{ needs.test-sqlite.result }}" + echo "MSSQL: ${{ needs.test-mssql.result }}" diff --git a/docs/databases.md b/docs/databases.md index ccc9250..4779f12 100644 --- a/docs/databases.md +++ b/docs/databases.md @@ -1,6 +1,13 @@ # Database Support -Fakestack supports three major database systems with native drivers and optimized connections. +Fakestack supports **6 major database systems** with native drivers and optimized connections: + +- **SQLite** - Lightweight, file-based database +- **MySQL** - Popular open-source relational database +- **PostgreSQL** - Advanced open-source relational database +- **MariaDB** - MySQL-compatible database with enhanced features +- **MS SQL Server** - Microsoft's enterprise database system +- **CockroachDB** - Distributed SQL database (PostgreSQL-compatible) ## SQLite @@ -432,6 +439,196 @@ mysql+mysqlconnector://username:password@host:port/database postgresql+psycopg2://username:password@host:port/database ``` +## MariaDB + +### Overview + +MariaDB is a MySQL-compatible database with enhanced performance, security features, and additional storage engines. + +**Pros:** +- Drop-in MySQL replacement +- Better performance than MySQL in many workloads +- More storage engines (Aria, ColumnStore, etc.) +- Active open-source development + +**Cons:** +- Some MySQL features not available +- Less widely adopted than MySQL + +### Configuration + +```json +{ + "database": { + "dbtype": "mariadb", + "username": "root", + "password": "yourpassword", + "host": "localhost:3306", + "database": "testdb" + } +} +``` + +### Connection Options + +| Option | Required | Default | Description | +|--------|----------|---------|-------------| +| `dbtype` | Yes | - | Must be `"mariadb"` | +| `username` | Yes | - | Database user | +| `password` | Yes | - | User password | +| `host` | Yes | - | Host and port (e.g., `localhost:3306`) | +| `database` | Yes | - | Database name | + +### Usage Example + +```bash +# Create and populate +fakestack -c -p -f schema.json +``` + +**Schema example:** +```json +{ + "database": { + "dbtype": "mariadb", + "username": "root", + "password": "password", + "host": "localhost:3306", + "database": "mydb" + }, + "tables": [...], + "populate": [...] +} +``` + +## MS SQL Server + +### Overview + +Microsoft SQL Server is an enterprise-grade relational database management system with advanced analytics and integration capabilities. + +**Pros:** +- Enterprise features and support +- Excellent Windows integration +- Advanced analytics and BI tools +- Strong security features + +**Cons:** +- Primarily Windows-focused (Linux support improving) +- Licensing costs for production +- More resource-intensive + +### Configuration + +```json +{ + "database": { + "dbtype": "mssql", + "username": "sa", + "password": "YourPassword123!", + "host": "localhost:1433", + "database": "testdb" + } +} +``` + +### Connection Options + +| Option | Required | Default | Description | +|--------|----------|---------|-------------| +| `dbtype` | Yes | - | Must be `"mssql"` | +| `username` | Yes | - | Database user (typically `sa`) | +| `password` | Yes | - | Strong password required | +| `host` | Yes | - | Host and port (e.g., `localhost:1433`) | +| `database` | Yes | - | Database name | + +### Docker Setup + +```bash +docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=YourPassword123!' \ + -p 1433:1433 --name mssql \ + mcr.microsoft.com/mssql/server:2022-latest +``` + +### Usage Example + +```bash +fakestack -c -p -f mssql-schema.json +``` + +### Special Considerations + +- **Password requirements**: Must be strong (uppercase, lowercase, numbers, special chars) +- **IDENTITY columns**: Use `autoincrement: true` for auto-incrementing primary keys +- **Connection timeout**: May need longer timeout for first connection + +## CockroachDB + +### Overview + +CockroachDB is a distributed SQL database that is PostgreSQL-compatible and designed for cloud-native applications. + +**Pros:** +- Horizontal scalability +- Built-in replication and consistency +- PostgreSQL wire protocol compatibility +- Resilient to node failures + +**Cons:** +- More complex setup than traditional databases +- Different performance characteristics +- Some PostgreSQL features not supported + +### Configuration + +```json +{ + "database": { + "dbtype": "cockroachdb", + "username": "root", + "password": "", + "host": "localhost:26257", + "database": "testdb" + } +} +``` + +### Connection Options + +| Option | Required | Default | Description | +|--------|----------|---------|-------------| +| `dbtype` | Yes | - | Must be `"cockroachdb"` | +| `username` | Yes | - | Database user (default: `root`) | +| `password` | No | `""` | Password (empty for insecure mode) | +| `host` | Yes | - | Host and port (default: `26257`) | +| `database` | Yes | - | Database name | + +### Docker Setup + +```bash +# Start single-node cluster (insecure for testing) +docker run -d -p 26257:26257 -p 8080:8080 \ + --name cockroach \ + cockroachdb/cockroach:latest start-single-node --insecure + +# Create database +docker exec -it cockroach ./cockroach sql --insecure \ + --execute="CREATE DATABASE testdb;" +``` + +### Usage Example + +```bash +fakestack -c -p -f cockroachdb-schema.json +``` + +### Special Considerations + +- Uses PostgreSQL driver internally +- Default port is `26257` (not `5432`) +- Supports most PostgreSQL SQL syntax +- Better suited for distributed deployments + ## Troubleshooting ### SQLite diff --git a/golang/database.go b/golang/database.go index 4087ce7..52b7d73 100644 --- a/golang/database.go +++ b/golang/database.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + _ "github.com/denisenkom/go-mssqldb" _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" @@ -46,12 +47,14 @@ func (db *Database) Close() error { // getDriver returns the SQL driver name func getDriver(dbType DbType) string { switch dbType { - case MySQL: + case MySQL, MariaDB: return "mysql" - case Postgres: + case Postgres, CockroachDB: return "postgres" case SQLite: return "sqlite3" + case MSSQL: + return "sqlserver" default: return "sqlite3" } @@ -60,14 +63,17 @@ func getDriver(dbType DbType) string { // buildConnectionString builds a database connection string func buildConnectionString(opts DbOptions) string { switch opts.DbType { - case MySQL: + case MySQL, MariaDB: return fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true", opts.Username, opts.Password, opts.Host, opts.Database) - case Postgres: + case Postgres, CockroachDB: return fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", opts.Username, opts.Password, opts.Host, opts.Database) case SQLite: return opts.Database + case MSSQL: + return fmt.Sprintf("sqlserver://%s:%s@%s?database=%s", + opts.Username, opts.Password, opts.Host, opts.Database) default: return opts.Database } @@ -125,8 +131,10 @@ func (db *Database) buildColumnDefinition(col ParsedTableColumn) string { def += " AUTOINCREMENT" } else if db.driver == "mysql" { def += " AUTO_INCREMENT" + } else if db.driver == "sqlserver" { + def += " IDENTITY(1,1)" } else { - // PostgreSQL uses SERIAL or BIGSERIAL + // PostgreSQL and CockroachDB use SERIAL or BIGSERIAL def = strings.Replace(def, sqlType, "SERIAL", 1) } } @@ -212,12 +220,12 @@ func (db *Database) Insert(tableName string, data map[string]interface{}) error func (db *Database) GetRandomValue(tableName, columnName string) (interface{}, error) { var query string switch db.driver { - case "sqlite3": + case "sqlite3", "postgres": query = fmt.Sprintf("SELECT %s FROM %s ORDER BY RANDOM() LIMIT 1", columnName, tableName) case "mysql": query = fmt.Sprintf("SELECT %s FROM %s ORDER BY RAND() LIMIT 1", columnName, tableName) - case "postgres": - query = fmt.Sprintf("SELECT %s FROM %s ORDER BY RANDOM() LIMIT 1", columnName, tableName) + case "sqlserver": + query = fmt.Sprintf("SELECT TOP 1 %s FROM %s ORDER BY NEWID()", columnName, tableName) } var value interface{} diff --git a/golang/go.mod b/golang/go.mod index fbd5aa7..c654835 100644 --- a/golang/go.mod +++ b/golang/go.mod @@ -9,4 +9,10 @@ require ( github.com/mattn/go-sqlite3 v1.14.24 ) -require filippo.io/edwards25519 v1.1.0 // indirect +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/denisenkom/go-mssqldb v0.12.3 // indirect + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect +) diff --git a/golang/go.sum b/golang/go.sum index 074cd93..67636db 100644 --- a/golang/go.sum +++ b/golang/go.sum @@ -1,10 +1,48 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/brianvoe/gofakeit/v7 v7.1.2 h1:vSKaVScNhWVpf1rlyEKSvO8zKZfuDtGqoIHT//iNNb8= github.com/brianvoe/gofakeit/v7 v7.1.2/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= +github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/golang/schema.go b/golang/schema.go index d432854..9b24926 100644 --- a/golang/schema.go +++ b/golang/schema.go @@ -10,9 +10,12 @@ import ( type DbType string const ( - MySQL DbType = "mysql" - Postgres DbType = "psql" - SQLite DbType = "sqlite" + MySQL DbType = "mysql" + Postgres DbType = "psql" + SQLite DbType = "sqlite" + MSSQL DbType = "mssql" + MariaDB DbType = "mariadb" + CockroachDB DbType = "cockroachdb" ) // DbOptions represents database connection configuration From 0ad51586068ea17326055493800ef22314a7ebd6 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 09:40:05 +0530 Subject: [PATCH 06/19] feat: enhance manual testing workflow for databases with individual toggle options and add CockroachDB support --- .github/workflows/test-databases.yml | 132 ++++++++++++++++++++++++--- CHANGELOG.md | 27 ++++++ 2 files changed, 145 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test-databases.yml b/.github/workflows/test-databases.yml index 8fa4ff1..91e1a36 100644 --- a/.github/workflows/test-databases.yml +++ b/.github/workflows/test-databases.yml @@ -4,9 +4,34 @@ name: Test Database Support on: workflow_dispatch: inputs: - test_all: - description: 'Test all databases' - required: true + test_mysql: + description: 'Test MySQL' + required: false + default: true + type: boolean + test_mariadb: + description: 'Test MariaDB' + required: false + default: true + type: boolean + test_postgres: + description: 'Test PostgreSQL' + required: false + default: true + type: boolean + test_sqlite: + description: 'Test SQLite' + required: false + default: true + type: boolean + test_mssql: + description: 'Test MS SQL Server' + required: false + default: true + type: boolean + test_cockroachdb: + description: 'Test CockroachDB' + required: false default: true type: boolean @@ -14,7 +39,7 @@ jobs: test-mysql: name: Test MySQL runs-on: ubuntu-latest - if: github.event.inputs.test_all == 'true' + if: github.event.inputs.test_mysql == 'true' services: mysql: @@ -94,7 +119,7 @@ jobs: test-mariadb: name: Test MariaDB runs-on: ubuntu-latest - if: github.event.inputs.test_all == 'true' + if: github.event.inputs.test_mariadb == 'true' services: mariadb: @@ -174,7 +199,7 @@ jobs: test-postgres: name: Test PostgreSQL runs-on: ubuntu-latest - if: github.event.inputs.test_all == 'true' + if: github.event.inputs.test_postgres == 'true' services: postgres: @@ -254,7 +279,7 @@ jobs: test-sqlite: name: Test SQLite runs-on: ubuntu-latest - if: github.event.inputs.test_all == 'true' + if: github.event.inputs.test_sqlite == 'true' steps: - uses: actions/checkout@v4 @@ -317,7 +342,7 @@ jobs: test-mssql: name: Test MS SQL Server runs-on: ubuntu-latest - if: github.event.inputs.test_all == 'true' + if: github.event.inputs.test_mssql == 'true' services: mssql: @@ -400,9 +425,86 @@ jobs: echo "Found $COUNT rows" [ "$COUNT" -eq 10 ] && echo "✅ MSSQL test passed" + test-cockroachdb: + name: Test CockroachDB + runs-on: ubuntu-latest + if: github.event.inputs.test_cockroachdb == 'true' + + steps: + - uses: actions/checkout@v4 + + - name: Start CockroachDB + run: | + docker run -d --name cockroach \ + -p 26257:26257 -p 8080:8080 \ + cockroachdb/cockroach:latest start-single-node --insecure + sleep 10 + + - name: Setup database + run: | + sudo apt-get update && sudo apt-get install -y postgresql-client + PGPASSWORD='' psql -h localhost -p 26257 -U root -c "CREATE DATABASE testdb;" + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache-dependency-path: golang/go.sum + + - name: Build fakestack + working-directory: golang + run: | + CGO_ENABLED=1 go build -o ../bin/fakestack-test + chmod +x ../bin/fakestack-test + + - name: Create test schema + run: | + cat > test-schema.json << 'EOF' + { + "database": { + "dbtype": "cockroachdb", + "username": "root", + "password": "", + "host": "localhost:26257", + "database": "testdb" + }, + "tables": [ + { + "name": "users", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "users", + "count": 10, + "fields": [ + {"name": "username", "generator": "user_name"}, + {"name": "email", "generator": "email"} + ] + } + ] + } + EOF + + - name: Test create and populate + run: | + ./bin/fakestack-test -c -f test-schema.json + ./bin/fakestack-test -p -f test-schema.json + + - name: Verify data + run: | + COUNT=$(PGPASSWORD='' psql -h localhost -p 26257 -U root testdb -t -c "SELECT COUNT(*) FROM users;" | xargs) + echo "Found $COUNT rows" + [ "$COUNT" -eq 10 ] && echo "✅ CockroachDB test passed" + summary: name: Test Summary - needs: [test-mysql, test-mariadb, test-postgres, test-sqlite, test-mssql] + needs: [test-mysql, test-mariadb, test-postgres, test-sqlite, test-mssql, test-cockroachdb] runs-on: ubuntu-latest if: always() @@ -411,8 +513,10 @@ jobs: run: | echo "🎯 Database Testing Complete" echo "==============================" - echo "MySQL: ${{ needs.test-mysql.result }}" - echo "MariaDB: ${{ needs.test-mariadb.result }}" - echo "PostgreSQL: ${{ needs.test-postgres.result }}" - echo "SQLite: ${{ needs.test-sqlite.result }}" - echo "MSSQL: ${{ needs.test-mssql.result }}" + echo "Selected databases:" + echo " MySQL: ${{ github.event.inputs.test_mysql == 'true' && needs.test-mysql.result || 'skipped' }}" + echo " MariaDB: ${{ github.event.inputs.test_mariadb == 'true' && needs.test-mariadb.result || 'skipped' }}" + echo " PostgreSQL: ${{ github.event.inputs.test_postgres == 'true' && needs.test-postgres.result || 'skipped' }}" + echo " SQLite: ${{ github.event.inputs.test_sqlite == 'true' && needs.test-sqlite.result || 'skipped' }}" + echo " MSSQL: ${{ github.event.inputs.test_mssql == 'true' && needs.test-mssql.result || 'skipped' }}" + echo " CockroachDB: ${{ github.event.inputs.test_cockroachdb == 'true' && needs.test-cockroachdb.result || 'skipped' }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 510a424..3b7e98a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **Database Support**: Added support for 3 additional database systems + - MariaDB - MySQL-compatible database with enhanced features + - MS SQL Server - Microsoft's enterprise database with IDENTITY syntax support + - CockroachDB - Distributed SQL database (PostgreSQL-compatible) +- **Manual Testing Workflow**: Created `.github/workflows/test-databases.yml` for on-demand database testing + - Tests all 6 supported databases in parallel + - Manual trigger via GitHub Actions UI (`workflow_dispatch`) + - Individual checkboxes for selective database testing (all selected by default) + - Docker-based testing for MySQL, PostgreSQL, MariaDB, MSSQL, SQLite, CockroachDB +- **Documentation**: Added comprehensive configuration examples and Docker setup for new databases +- **Version Selector**: Added version dropdown to documentation (powered by mike) +- **Uninstall Script**: Created `scripts/uninstall.sh` for removing fakestack from npm, pip, and Homebrew + +### Changed +- **Homebrew Tap**: Renamed from `homebrew-fakestack` to `homebrew-packages` for future tool support + - New installation: `brew tap 0xdps/packages && brew install fakestack` + - Prepares tap for additional tools beyond fakestack +- **Database Driver**: Added Microsoft SQL Server driver (`github.com/denisenkom/go-mssqldb`) +- **Documentation**: Updated all installation instructions to use new tap name + +### Technical Details +- Total supported databases: **6** (SQLite, MySQL, PostgreSQL, MariaDB, MSSQL, CockroachDB) +- MariaDB uses MySQL driver (drop-in compatible) +- CockroachDB uses PostgreSQL driver +- MSSQL implements IDENTITY autoincrement and NEWID() for random selection + ## [1.0.1] - 2025-11-17 ### Added From 9d4d380f694255d13d620a2632f46cb268cf0823 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 09:44:08 +0530 Subject: [PATCH 07/19] fix: update sqlcmd path for MS SQL Server verification step in test workflow --- .github/workflows/test-databases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-databases.yml b/.github/workflows/test-databases.yml index 91e1a36..b7b0ea0 100644 --- a/.github/workflows/test-databases.yml +++ b/.github/workflows/test-databases.yml @@ -420,7 +420,7 @@ jobs: - name: Verify data run: | COUNT=$(docker exec $(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) \ - /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' -d testdb -h -1 \ + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' -d testdb -h -1 -C \ -Q "SET NOCOUNT ON; SELECT COUNT(*) FROM users;" | xargs) echo "Found $COUNT rows" [ "$COUNT" -eq 10 ] && echo "✅ MSSQL test passed" From 0adab0d177472838f20d2bd87d83e54a19768b07 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 09:54:18 +0530 Subject: [PATCH 08/19] fix: update sqlcmd path for MS SQL Server setup in test workflow --- .github/workflows/test-databases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-databases.yml b/.github/workflows/test-databases.yml index b7b0ea0..e4fcebd 100644 --- a/.github/workflows/test-databases.yml +++ b/.github/workflows/test-databases.yml @@ -369,7 +369,7 @@ jobs: - name: Setup database run: | docker exec $(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) \ - /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' \ + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' -C \ -Q "CREATE DATABASE testdb;" - name: Build fakestack From cc92e5f1063461efb4a20f3b126694524eba1e89 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 09:58:20 +0530 Subject: [PATCH 09/19] feat: add SQL Server compatibility for table creation, dropping, and querying --- golang/database.go | 51 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/golang/database.go b/golang/database.go index 52b7d73..80e59ac 100644 --- a/golang/database.go +++ b/golang/database.go @@ -93,7 +93,12 @@ func (db *Database) CreateTables() error { // DropTables drops all tables defined in the schema func (db *Database) DropTables() error { for _, table := range db.schema.Tables { - query := fmt.Sprintf("DROP TABLE IF EXISTS %s", table.Name) + var query string + if db.driver == "sqlserver" { + query = fmt.Sprintf("IF OBJECT_ID('[%s]', 'U') IS NOT NULL DROP TABLE [%s]", table.Name, table.Name) + } else { + query = fmt.Sprintf("DROP TABLE IF EXISTS %s", table.Name) + } if _, err := db.conn.Exec(query); err != nil { return fmt.Errorf("failed to drop table %s: %w", table.Name, err) } @@ -111,8 +116,14 @@ func (db *Database) createTable(table DbTable) error { columns = append(columns, colDef) } - query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (\n %s\n)", - table.Name, strings.Join(columns, ",\n ")) + var query string + if db.driver == "sqlserver" { + query = fmt.Sprintf("IF OBJECT_ID('[%s]', 'U') IS NULL CREATE TABLE [%s] (\n %s\n)", + table.Name, table.Name, strings.Join(columns, ",\n ")) + } else { + query = fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (\n %s\n)", + table.Name, strings.Join(columns, ",\n ")) + } _, err := db.conn.Exec(query) return err @@ -121,7 +132,11 @@ func (db *Database) createTable(table DbTable) error { // buildColumnDefinition builds a SQL column definition func (db *Database) buildColumnDefinition(col ParsedTableColumn) string { sqlType := db.getSQLType(col.Type) - def := fmt.Sprintf("%s %s", col.Name, sqlType) + colName := col.Name + if db.driver == "sqlserver" { + colName = fmt.Sprintf("[%s]", col.Name) + } + def := fmt.Sprintf("%s %s", colName, sqlType) // Handle options if primary, ok := col.Options["primary_key"].(bool); ok && primary { @@ -197,7 +212,11 @@ func (db *Database) Insert(tableName string, data map[string]interface{}) error i := 1 for col, val := range data { - columns = append(columns, col) + if db.driver == "sqlserver" { + columns = append(columns, fmt.Sprintf("[%s]", col)) + } else { + columns = append(columns, col) + } if db.driver == "postgres" { placeholders = append(placeholders, fmt.Sprintf("$%d", i)) } else { @@ -207,8 +226,15 @@ func (db *Database) Insert(tableName string, data map[string]interface{}) error i++ } + var tableName_quoted string + if db.driver == "sqlserver" { + tableName_quoted = fmt.Sprintf("[%s]", tableName) + } else { + tableName_quoted = tableName + } + query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", - tableName, + tableName_quoted, strings.Join(columns, ", "), strings.Join(placeholders, ", ")) @@ -219,13 +245,20 @@ func (db *Database) Insert(tableName string, data map[string]interface{}) error // GetRandomValue gets a random value from a column func (db *Database) GetRandomValue(tableName, columnName string) (interface{}, error) { var query string + tableQuoted := tableName + columnQuoted := columnName + if db.driver == "sqlserver" { + tableQuoted = fmt.Sprintf("[%s]", tableName) + columnQuoted = fmt.Sprintf("[%s]", columnName) + } + switch db.driver { case "sqlite3", "postgres": - query = fmt.Sprintf("SELECT %s FROM %s ORDER BY RANDOM() LIMIT 1", columnName, tableName) + query = fmt.Sprintf("SELECT %s FROM %s ORDER BY RANDOM() LIMIT 1", columnQuoted, tableQuoted) case "mysql": - query = fmt.Sprintf("SELECT %s FROM %s ORDER BY RAND() LIMIT 1", columnName, tableName) + query = fmt.Sprintf("SELECT %s FROM %s ORDER BY RAND() LIMIT 1", columnQuoted, tableQuoted) case "sqlserver": - query = fmt.Sprintf("SELECT TOP 1 %s FROM %s ORDER BY NEWID()", columnName, tableName) + query = fmt.Sprintf("SELECT TOP 1 %s FROM %s ORDER BY NEWID()", columnQuoted, tableQuoted) } var value interface{} From 28390706459231c60eee60344f03ca3d1579e245 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 10:09:04 +0530 Subject: [PATCH 10/19] fix: update SQL parameter placeholders for SQL Server and PostgreSQL in Insert method --- golang/database.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/golang/database.go b/golang/database.go index 80e59ac..b04135b 100644 --- a/golang/database.go +++ b/golang/database.go @@ -214,13 +214,14 @@ func (db *Database) Insert(tableName string, data map[string]interface{}) error for col, val := range data { if db.driver == "sqlserver" { columns = append(columns, fmt.Sprintf("[%s]", col)) + placeholders = append(placeholders, fmt.Sprintf("@p%d", i)) } else { columns = append(columns, col) - } - if db.driver == "postgres" { - placeholders = append(placeholders, fmt.Sprintf("$%d", i)) - } else { - placeholders = append(placeholders, "?") + if db.driver == "postgres" { + placeholders = append(placeholders, fmt.Sprintf("$%d", i)) + } else { + placeholders = append(placeholders, "?") + } } values = append(values, val) i++ From 78249abe7441a5cc4d6c2e79edc38682e15a484f Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 10:40:30 +0530 Subject: [PATCH 11/19] Add schema generator script for multiple database types and templates - Implemented a bash script to generate database schemas for SQLite, MySQL, PostgreSQL, MariaDB, MS SQL Server, and CockroachDB. - Added user prompts for database credentials, schema templates, and output filename. - Supported schema generation for various templates including Users, Employees, Products, Orders, Customers, Blog Posts, Inventory, Transactions, Students, and Tasks. - Output schema is formatted as JSON and saved to a specified file. - Included instructions for creating tables and populating data using the generated schema. --- CHANGELOG.md | 20 ++ README.md | 28 +- golang/generator.go | 13 +- golang/main.go | 22 +- golang/templates.go | 542 +++++++++++++++++++++++++++++ scripts/generate-schema.sh | 696 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1316 insertions(+), 5 deletions(-) create mode 100644 golang/templates.go create mode 100755 scripts/generate-schema.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7e98a..dd0538a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Interactive Schema Generator**: New `-g` / `--generate` CLI flag for creating schemas + - Built directly into fakestack CLI (no separate script needed) + - 10 pre-built templates (Users, Employees, Products, Orders, Customers, Blog Posts, Inventory, Transactions, Students, Tasks) + - Support for all 6 database types + - Customizable database credentials and output filename + - Automatic row count suggestions per template + - Usage: `fakestack -g .` or `fakestack -g my-schema.json` + - Implementation in `golang/templates.go` for clean code organization +- **New Generators**: Added `integer`, `float`, and `decimal` generators with min/max range support + - `integer` / `random_int` - Generate integers with configurable min/max range + - `float` / `decimal` - Generate floating-point numbers with configurable min/max range + - Backward compatible with existing `random_int` generator - **Database Support**: Added support for 3 additional database systems - MariaDB - MySQL-compatible database with enhanced features - MS SQL Server - Microsoft's enterprise database with IDENTITY syntax support @@ -28,6 +40,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Database Driver**: Added Microsoft SQL Server driver (`github.com/denisenkom/go-mssqldb`) - **Documentation**: Updated all installation instructions to use new tap name +### Fixed +- **MSSQL Compatibility**: Fixed SQL syntax issues for MS SQL Server + - Added square brackets `[table]` and `[column]` around identifiers + - Changed placeholders from `?` to `@p1, @p2, @p3` format + - Updated `CREATE TABLE` to use `IF OBJECT_ID` syntax + - Updated `DROP TABLE` to use `IF OBJECT_ID` syntax + - Fixed sqlcmd path in workflow to `/opt/mssql-tools18/bin/sqlcmd` with `-C` flag + ### Technical Details - Total supported databases: **6** (SQLite, MySQL, PostgreSQL, MariaDB, MSSQL, CockroachDB) - MariaDB uses MySQL driver (drop-in compatible) diff --git a/README.md b/README.md index 63afa04..378de6b 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,33 @@ Pre-built binaries available on [GitHub Releases](https://github.com/0xdps/fake- ## 🚀 Quick Start -### 1. Download Example Schema +### 1. Generate a Schema (Interactive) + +Use the built-in interactive generator: ```bash -fakestack -d . +fakestack -g . +# or specify output filename +fakestack -g my-schema.json ``` -This creates a `schema.json` file in the current directory. +Choose from 10 pre-built templates: +- **Users** - Basic user management +- **Employees** - Employee records with departments +- **Products** - E-commerce products +- **Orders** - Order management system +- **Customers** - Customer database +- **Blog Posts** - Content management +- **Inventory** - Stock management +- **Transactions** - Financial records +- **Students** - Educational records +- **Tasks** - Task management + +Or download a basic example: + +```bash +fakestack -d . +``` ### 2. Create Tables and Populate Data @@ -98,7 +118,9 @@ Options: -c, --create-table Create database tables from schema -p, --populate-data Populate tables with fake data -f, --file Path to JSON schema file + -g, --generate Generate schema interactively (use '.' for default filename) -d, --download-schema Download example schema + -v, --version Show version information -h, --help Display help message ``` diff --git a/golang/generator.go b/golang/generator.go index f9d6f84..e15d39f 100644 --- a/golang/generator.go +++ b/golang/generator.go @@ -109,7 +109,7 @@ func (g *Generator) Generate(field PopulateField, commons map[string]interface{} return g.fake.Word(), nil // Numbers - case "random_int": + case "integer", "random_int": min, max := 0, 100 if argsMap, ok := args.(map[string]interface{}); ok { if minVal, ok := argsMap["min"].(float64); ok { @@ -120,6 +120,17 @@ func (g *Generator) Generate(field PopulateField, commons map[string]interface{} } } return g.fake.IntRange(min, max), nil + case "float", "decimal": + min, max := 0.0, 100.0 + if argsMap, ok := args.(map[string]interface{}); ok { + if minVal, ok := argsMap["min"].(float64); ok { + min = minVal + } + if maxVal, ok := argsMap["max"].(float64); ok { + max = maxVal + } + } + return g.fake.Float64Range(min, max), nil case "random_digit": return g.fake.Digit(), nil case "random_number": diff --git a/golang/main.go b/golang/main.go index 2e1455e..f0c79eb 100644 --- a/golang/main.go +++ b/golang/main.go @@ -78,6 +78,9 @@ func main() { downloadSchema := flag.String("d", "", "download example schema to specified path (use '.' for current directory)") downloadSchemaLong := flag.String("download-schema", "", "download example schema to specified path") + generateSchema := flag.String("g", "", "generate schema interactively (specify output filename or use '.' for default)") + generateSchemaLong := flag.String("generate", "", "generate schema interactively (specify output filename)") + versionFlag := flag.Bool("v", false, "show version information") versionFlagLong := flag.Bool("version", false, "show version information") @@ -107,6 +110,23 @@ func main() { if download == "" { download = *downloadSchemaLong } + generate := *generateSchema + if generate == "" { + generate = *generateSchemaLong + } + + // Handle generate schema (interactive) + if generate != "" { + outputFile := "" + if generate != "." { + outputFile = generate + } + if err := RunInteractiveGenerator(outputFile); err != nil { + fmt.Fprintf(os.Stderr, "Error generating schema: %v\n", err) + os.Exit(1) + } + return + } // Handle download schema if download != "" { @@ -121,7 +141,7 @@ func main() { // Validate arguments if !create && !populate { fmt.Fprintf(os.Stderr, "Error: no arguments provided!!!\n") - fmt.Fprintf(os.Stderr, "Use -c to create tables, -p to populate data, or -d to download example schema\n") + fmt.Fprintf(os.Stderr, "Use -c to create tables, -p to populate data, -g to generate schema, or -d to download example\n") flag.Usage() os.Exit(1) } diff --git a/golang/templates.go b/golang/templates.go new file mode 100644 index 0000000..0eccd77 --- /dev/null +++ b/golang/templates.go @@ -0,0 +1,542 @@ +package main + +import ( +"bufio" +"fmt" +"os" +"strconv" +"strings" +) + +// SchemaTemplate represents a pre-built schema template +type SchemaTemplate struct { + Name string + Description string + TableName string + RowCount int + Generator func(dbConfig DatabaseConfig) string +} + +// DatabaseConfig holds database configuration +type DatabaseConfig struct { + DBType string + Username string + Password string + Host string + Port string + Database string +} + +// RunInteractiveGenerator runs the interactive schema generator +func RunInteractiveGenerator(outputFile string) error { + reader := bufio.NewReader(os.Stdin) + + fmt.Println("========================================") + fmt.Println(" Fakestack Schema Generator") + fmt.Println("========================================") + fmt.Println() + + // Ask for database type + fmt.Println("Select database type:") + fmt.Println("1) SQLite") + fmt.Println("2) MySQL") + fmt.Println("3) PostgreSQL") + fmt.Println("4) MariaDB") + fmt.Println("5) MS SQL Server") + fmt.Println("6) CockroachDB") + fmt.Print("Enter your choice (1-6): ") + + dbChoice, _ := reader.ReadString('\n') + dbChoice = strings.TrimSpace(dbChoice) + + config := DatabaseConfig{} + switch dbChoice { + case "1": + config.DBType = "sqlite" + case "2": + config.DBType = "mysql" + case "3": + config.DBType = "postgres" + case "4": + config.DBType = "mariadb" + case "5": + config.DBType = "mssql" + case "6": + config.DBType = "cockroachdb" + default: + return fmt.Errorf("invalid database choice") + } + + // Ask for credentials (skip for SQLite) + if config.DBType != "sqlite" { + fmt.Println() + fmt.Print("Enter database username (default: root): ") + config.Username, _ = reader.ReadString('\n') + config.Username = strings.TrimSpace(config.Username) + if config.Username == "" { + config.Username = "root" + } + + fmt.Print("Enter database password (default: password): ") + config.Password, _ = reader.ReadString('\n') + config.Password = strings.TrimSpace(config.Password) + if config.Password == "" { + config.Password = "password" + } + + fmt.Print("Enter database host (default: localhost): ") + config.Host, _ = reader.ReadString('\n') + config.Host = strings.TrimSpace(config.Host) + if config.Host == "" { + config.Host = "localhost" + } + + // Set default ports + defaultPort := "3306" + switch config.DBType { + case "mysql", "mariadb": + defaultPort = "3306" + case "postgres": + defaultPort = "5432" + case "mssql": + defaultPort = "1433" + case "cockroachdb": + defaultPort = "26257" + } + + fmt.Printf("Enter database port (default: %s): ", defaultPort) + config.Port, _ = reader.ReadString('\n') + config.Port = strings.TrimSpace(config.Port) + if config.Port == "" { + config.Port = defaultPort + } + + fmt.Print("Enter database name (default: testdb): ") + config.Database, _ = reader.ReadString('\n') + config.Database = strings.TrimSpace(config.Database) + if config.Database == "" { + config.Database = "testdb" + } + } else { + config.Database = "test.db" + } + + // Ask for schema template + fmt.Println() + fmt.Println("Select schema template:") + templates := GetSchemaTemplates() + for i, tmpl := range templates { + fmt.Printf("%d) %s - %s\n", i+1, tmpl.Name, tmpl.Description) + } + fmt.Printf("Enter your choice (1-%d): ", len(templates)) + + schemaChoice, _ := reader.ReadString('\n') + schemaChoice = strings.TrimSpace(schemaChoice) + schemaIdx, err := strconv.Atoi(schemaChoice) + if err != nil || schemaIdx < 1 || schemaIdx > len(templates) { + return fmt.Errorf("invalid schema choice") + } + + selectedTemplate := templates[schemaIdx-1] + + // Ask for output filename if not provided + if outputFile == "" { + fmt.Println() + fmt.Print("Enter output filename (default: schema.json): ") + outputFile, _ = reader.ReadString('\n') + outputFile = strings.TrimSpace(outputFile) + if outputFile == "" { + outputFile = "schema.json" + } + } + + // Generate schema + schemaContent := selectedTemplate.Generator(config) + + // Write to file + if err := os.WriteFile(outputFile, []byte(schemaContent), 0644); err != nil { + return fmt.Errorf("failed to write schema file: %w", err) + } + + fmt.Println() + fmt.Println("✓ Schema file generated successfully!") + fmt.Printf("File: %s\n", outputFile) + fmt.Printf("Database: %s\n", config.DBType) + fmt.Printf("Table: %s\n", selectedTemplate.TableName) + fmt.Printf("Rows: %d\n", selectedTemplate.RowCount) + fmt.Println() + fmt.Println("Next steps:") + fmt.Printf(" 1. Create tables: fakestack -c -f %s\n", outputFile) + fmt.Printf(" 2. Populate data: fakestack -p -f %s\n", outputFile) + fmt.Printf(" 3. Or do both: fakestack -c -p -f %s\n", outputFile) + fmt.Println() + + return nil +} + +// GetSchemaTemplates returns all available schema templates +func GetSchemaTemplates() []SchemaTemplate { + return []SchemaTemplate{ + {Name: "Users", Description: "Basic user management (id, username, email, created_at)", TableName: "users", RowCount: 50, Generator: generateUsersSchema}, + {Name: "Employees", Description: "Employee records (id, first_name, last_name, email, department, salary, hire_date)", TableName: "employees", RowCount: 100, Generator: generateEmployeesSchema}, + {Name: "Products", Description: "E-commerce products (id, name, description, price, stock, category)", TableName: "products", RowCount: 200, Generator: generateProductsSchema}, + {Name: "Orders", Description: "Order management (id, order_number, customer_email, total_amount, status, order_date)", TableName: "orders", RowCount: 500, Generator: generateOrdersSchema}, + {Name: "Customers", Description: "Customer database (id, name, email, phone, address, city, country)", TableName: "customers", RowCount: 150, Generator: generateCustomersSchema}, + {Name: "Blog Posts", Description: "Content management (id, title, content, author, published_at, views)", TableName: "posts", RowCount: 100, Generator: generateBlogPostsSchema}, + {Name: "Inventory", Description: "Stock management (id, item_name, sku, quantity, location, last_updated)", TableName: "inventory", RowCount: 300, Generator: generateInventorySchema}, + {Name: "Transactions", Description: "Financial records (id, transaction_id, amount, type, status, timestamp)", TableName: "transactions", RowCount: 1000, Generator: generateTransactionsSchema}, + {Name: "Students", Description: "Educational records (id, student_id, name, email, grade, enrollment_date)", TableName: "students", RowCount: 200, Generator: generateStudentsSchema}, + {Name: "Tasks", Description: "Task management (id, title, description, priority, status, due_date, assignee)", TableName: "tasks", RowCount: 150, Generator: generateTasksSchema}, + } +} + +// Helper function to build database config JSON +func buildDatabaseConfigJSON(config DatabaseConfig) string { + if config.DBType == "sqlite" { + return fmt.Sprintf(` "database": { + "dbtype": "%s", + "database": "%s" + }`, config.DBType, config.Database) + } + hostWithPort := fmt.Sprintf("%s:%s", config.Host, config.Port) + return fmt.Sprintf(` "database": { + "dbtype": "%s", + "username": "%s", + "password": "%s", + "host": "%s", + "database": "%s" + }`, config.DBType, config.Username, config.Password, hostWithPort, config.Database) +} + +// Schema generator functions +func generateUsersSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "users", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false, "unique": true}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false, "unique": true}}, + {"name": "created_at", "type": "datetime", "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "users", + "count": 50, + "fields": [ + {"name": "username", "generator": "user_name"}, + {"name": "email", "generator": "email"}, + {"name": "created_at", "generator": "past_date"} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateEmployeesSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "employees", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false, "unique": true}}, + {"name": "department", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "salary", "type": {"name": "decimal", "args": {"precision": 10, "scale": 2}}, "options": {"nullable": false}}, + {"name": "hire_date", "type": "date", "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "employees", + "count": 100, + "fields": [ + {"name": "first_name", "generator": "first_name"}, + {"name": "last_name", "generator": "last_name"}, + {"name": "email", "generator": "email"}, + {"name": "department", "generator": "random_from", "args": ["Engineering", "Sales", "Marketing", "HR", "Finance", "Operations"]}, + {"name": "salary", "generator": "float", "args": {"min": 40000, "max": 150000}}, + {"name": "hire_date", "generator": "past_date"} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateProductsSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "products", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "name", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}}, + {"name": "description", "type": "text", "options": {"nullable": true}}, + {"name": "price", "type": {"name": "decimal", "args": {"precision": 10, "scale": 2}}, "options": {"nullable": false}}, + {"name": "stock", "type": "integer", "options": {"nullable": false}}, + {"name": "category", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "products", + "count": 200, + "fields": [ + {"name": "name", "generator": "word"}, + {"name": "description", "generator": "sentence", "args": {"word_count": 10}}, + {"name": "price", "generator": "float", "args": {"min": 9.99, "max": 999.99}}, + {"name": "stock", "generator": "integer", "args": {"min": 0, "max": 500}}, + {"name": "category", "generator": "random_from", "args": ["Electronics", "Clothing", "Books", "Home & Garden", "Toys", "Sports"]} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateOrdersSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "orders", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "order_number", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false, "unique": true}}, + {"name": "customer_email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}}, + {"name": "total_amount", "type": {"name": "decimal", "args": {"precision": 10, "scale": 2}}, "options": {"nullable": false}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}, "options": {"nullable": false}}, + {"name": "order_date", "type": "datetime", "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "orders", + "count": 500, + "fields": [ + {"name": "order_number", "generator": "uuid"}, + {"name": "customer_email", "generator": "email"}, + {"name": "total_amount", "generator": "float", "args": {"min": 10.00, "max": 1000.00}}, + {"name": "status", "generator": "random_from", "args": ["pending", "processing", "shipped", "delivered", "cancelled"]}, + {"name": "order_date", "generator": "past_date"} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateCustomersSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "customers", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "name", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false, "unique": true}}, + {"name": "phone", "type": {"name": "string", "args": {"length": 20}}, "options": {"nullable": true}}, + {"name": "address", "type": {"name": "string", "args": {"length": 200}}, "options": {"nullable": true}}, + {"name": "city", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": true}}, + {"name": "country", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "customers", + "count": 150, + "fields": [ + {"name": "name", "generator": "name"}, + {"name": "email", "generator": "email"}, + {"name": "phone", "generator": "phone_number"}, + {"name": "address", "generator": "address"}, + {"name": "city", "generator": "city"}, + {"name": "country", "generator": "country"} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateBlogPostsSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "posts", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "title", "type": {"name": "string", "args": {"length": 200}}, "options": {"nullable": false}}, + {"name": "content", "type": "text", "options": {"nullable": false}}, + {"name": "author", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}}, + {"name": "published_at", "type": "datetime", "options": {"nullable": false}}, + {"name": "views", "type": "integer", "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "posts", + "count": 100, + "fields": [ + {"name": "title", "generator": "sentence", "args": {"word_count": 6}}, + {"name": "content", "generator": "paragraph", "args": {"sentence_count": 5}}, + {"name": "author", "generator": "name"}, + {"name": "published_at", "generator": "past_date"}, + {"name": "views", "generator": "integer", "args": {"min": 0, "max": 10000}} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateInventorySchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "inventory", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "item_name", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}}, + {"name": "sku", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false, "unique": true}}, + {"name": "quantity", "type": "integer", "options": {"nullable": false}}, + {"name": "location", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, + {"name": "last_updated", "type": "datetime", "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "inventory", + "count": 300, + "fields": [ + {"name": "item_name", "generator": "word"}, + {"name": "sku", "generator": "uuid"}, + {"name": "quantity", "generator": "integer", "args": {"min": 0, "max": 1000}}, + {"name": "location", "generator": "random_from", "args": ["Warehouse A", "Warehouse B", "Store 1", "Store 2", "Store 3"]}, + {"name": "last_updated", "generator": "past_date"} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateTransactionsSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "transactions", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "transaction_id", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false, "unique": true}}, + {"name": "amount", "type": {"name": "decimal", "args": {"precision": 10, "scale": 2}}, "options": {"nullable": false}}, + {"name": "type", "type": {"name": "string", "args": {"length": 20}}, "options": {"nullable": false}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}, "options": {"nullable": false}}, + {"name": "timestamp", "type": "datetime", "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "transactions", + "count": 1000, + "fields": [ + {"name": "transaction_id", "generator": "uuid"}, + {"name": "amount", "generator": "float", "args": {"min": 1.00, "max": 10000.00}}, + {"name": "type", "generator": "random_from", "args": ["credit", "debit", "transfer", "refund"]}, + {"name": "status", "generator": "random_from", "args": ["completed", "pending", "failed", "cancelled"]}, + {"name": "timestamp", "generator": "past_date"} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateStudentsSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "students", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "student_id", "type": {"name": "string", "args": {"length": 20}}, "options": {"nullable": false, "unique": true}}, + {"name": "name", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false, "unique": true}}, + {"name": "grade", "type": {"name": "string", "args": {"length": 10}}, "options": {"nullable": false}}, + {"name": "enrollment_date", "type": "date", "options": {"nullable": false}} + ] + } + ], + "populate": [ + { + "name": "students", + "count": 200, + "fields": [ + {"name": "student_id", "generator": "uuid"}, + {"name": "name", "generator": "name"}, + {"name": "email", "generator": "email"}, + {"name": "grade", "generator": "random_from", "args": ["A", "B", "C", "D", "F"]}, + {"name": "enrollment_date", "generator": "past_date"} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} + +func generateTasksSchema(config DatabaseConfig) string { + return fmt.Sprintf(`{ +%s, + "tables": [ + { + "name": "tasks", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "title", "type": {"name": "string", "args": {"length": 200}}, "options": {"nullable": false}}, + {"name": "description", "type": "text", "options": {"nullable": true}}, + {"name": "priority", "type": {"name": "string", "args": {"length": 20}}, "options": {"nullable": false}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}, "options": {"nullable": false}}, + {"name": "due_date", "type": "date", "options": {"nullable": true}}, + {"name": "assignee", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": true}} + ] + } + ], + "populate": [ + { + "name": "tasks", + "count": 150, + "fields": [ + {"name": "title", "generator": "sentence", "args": {"word_count": 5}}, + {"name": "description", "generator": "paragraph", "args": {"sentence_count": 3}}, + {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]}, + {"name": "status", "generator": "random_from", "args": ["todo", "in_progress", "review", "done"]}, + {"name": "due_date", "generator": "future_date"}, + {"name": "assignee", "generator": "name"} + ] + } + ] +} +`, buildDatabaseConfigJSON(config)) +} diff --git a/scripts/generate-schema.sh b/scripts/generate-schema.sh new file mode 100755 index 0000000..a6c3de3 --- /dev/null +++ b/scripts/generate-schema.sh @@ -0,0 +1,696 @@ +#!/bin/bash + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE} Fakestack Schema Generator${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Ask for database type +echo -e "${YELLOW}Select database type:${NC}" +echo "1) SQLite" +echo "2) MySQL" +echo "3) PostgreSQL" +echo "4) MariaDB" +echo "5) MS SQL Server" +echo "6) CockroachDB" +read -p "Enter your choice (1-6): " db_choice + +case $db_choice in + 1) dbtype="sqlite" ;; + 2) dbtype="mysql" ;; + 3) dbtype="postgres" ;; + 4) dbtype="mariadb" ;; + 5) dbtype="mssql" ;; + 6) dbtype="cockroachdb" ;; + *) echo -e "${RED}Invalid choice!${NC}"; exit 1 ;; +esac + +# Ask for database credentials (skip for SQLite) +if [ "$dbtype" != "sqlite" ]; then + echo "" + read -p "Enter database username (default: root): " username + username=${username:-root} + + read -p "Enter database password (default: password): " password + password=${password:-password} + + read -p "Enter database host (default: localhost): " host + host=${host:-localhost} + + # Set default ports + case $dbtype in + mysql|mariadb) default_port="3306" ;; + postgres) default_port="5432" ;; + mssql) default_port="1433" ;; + cockroachdb) default_port="26257" ;; + esac + + read -p "Enter database port (default: $default_port): " port + port=${port:-$default_port} + host_with_port="${host}:${port}" + + read -p "Enter database name (default: testdb): " database + database=${database:-testdb} +else + database="test.db" +fi + +# Ask for schema template +echo "" +echo -e "${YELLOW}Select schema template:${NC}" +echo "1) Users - Basic user management (id, username, email, created_at)" +echo "2) Employees - Employee records (id, first_name, last_name, email, department, salary, hire_date)" +echo "3) Products - E-commerce products (id, name, description, price, stock, category)" +echo "4) Orders - Order management (id, order_number, customer_email, total_amount, status, order_date)" +echo "5) Customers - Customer database (id, name, email, phone, address, city, country)" +echo "6) Blog Posts - Content management (id, title, content, author, published_at, views)" +echo "7) Inventory - Stock management (id, item_name, sku, quantity, location, last_updated)" +echo "8) Transactions - Financial records (id, transaction_id, amount, type, status, timestamp)" +echo "9) Students - Educational records (id, student_id, name, email, grade, enrollment_date)" +echo "10) Tasks - Task management (id, title, description, priority, status, due_date, assignee)" +read -p "Enter your choice (1-10): " schema_choice + +# Ask for output filename +echo "" +read -p "Enter output filename (default: schema.json): " filename +filename=${filename:-schema.json} + +# Generate schema based on choice +case $schema_choice in + 1) # Users + table_name="users" + row_count=50 + schema_json=$(cat < "$filename" + +echo "" +echo -e "${GREEN}✓ Schema file generated successfully!${NC}" +echo -e "${BLUE}File: ${NC}$filename" +echo -e "${BLUE}Database: ${NC}$dbtype" +echo -e "${BLUE}Table: ${NC}$table_name" +echo -e "${BLUE}Rows: ${NC}$row_count" +echo "" +echo -e "${YELLOW}Next steps:${NC}" +echo -e " 1. Create tables: ${GREEN}fakestack -c -f $filename${NC}" +echo -e " 2. Populate data: ${GREEN}fakestack -p -f $filename${NC}" +echo -e " 3. Or do both: ${GREEN}fakestack -c -p -f $filename${NC}" +echo "" From fd10f873d58b19d1e02f561cd8db12c2c67343da Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 10:45:27 +0530 Subject: [PATCH 12/19] fix: add GO_*.md to .gitignore to exclude Go documentation files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 31f0cc8..7a455ae 100644 --- a/.gitignore +++ b/.gitignore @@ -546,3 +546,4 @@ bin/ .vercel/ PUBLISHING_CHECKLIST.md +GO_*.md From 3d21c29a74854a9a754e4e6329933b6e7cad941d Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 10:59:43 +0530 Subject: [PATCH 13/19] feat: update test-databases.yml to use comprehensive_test table with enhanced schema and data population --- .github/workflows/test-databases.yml | 504 ++++++++++++++++++++++++--- 1 file changed, 456 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test-databases.yml b/.github/workflows/test-databases.yml index e4fcebd..08e55db 100644 --- a/.github/workflows/test-databases.yml +++ b/.github/workflows/test-databases.yml @@ -83,21 +83,89 @@ jobs: }, "tables": [ { - "name": "users", + "name": "comprehensive_test", "columns": [ {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "latitude", "type": "float"}, + {"name": "longitude", "type": "float"}, + {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, + {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, + {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, + {"name": "date_field", "type": "date"}, + {"name": "past_date", "type": "date"}, + {"name": "future_date", "type": "date"}, + {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "paragraph", "type": "text"}, + {"name": "random_int", "type": "integer"}, + {"name": "salary", "type": "float"}, + {"name": "price", "type": "float"}, + {"name": "is_active", "type": "boolean"}, + {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} ] } ], "populate": [ { - "name": "users", + "name": "comprehensive_test", "count": 10, "fields": [ + {"name": "first_name", "generator": "first_name"}, + {"name": "last_name", "generator": "last_name"}, + {"name": "full_name", "generator": "name"}, {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"} + {"name": "email", "generator": "email"}, + {"name": "password", "generator": "password"}, + {"name": "gender", "generator": "gender"}, + {"name": "address", "generator": "address"}, + {"name": "street_address", "generator": "street_address"}, + {"name": "city", "generator": "city"}, + {"name": "state", "generator": "state"}, + {"name": "country", "generator": "country"}, + {"name": "postcode", "generator": "postcode"}, + {"name": "latitude", "generator": "latitude"}, + {"name": "longitude", "generator": "longitude"}, + {"name": "company", "generator": "company"}, + {"name": "job_title", "generator": "job"}, + {"name": "phone", "generator": "phone_number"}, + {"name": "url", "generator": "url"}, + {"name": "domain", "generator": "domain_name"}, + {"name": "ipv4", "generator": "ipv4"}, + {"name": "ipv6", "generator": "ipv6"}, + {"name": "mac_address", "generator": "mac_address"}, + {"name": "date_field", "generator": "date"}, + {"name": "past_date", "generator": "past_date"}, + {"name": "future_date", "generator": "future_date"}, + {"name": "word", "generator": "word"}, + {"name": "sentence", "generator": "sentence"}, + {"name": "paragraph", "generator": "paragraph"}, + {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, + {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, + {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, + {"name": "is_active", "generator": "boolean"}, + {"name": "uuid", "generator": "uuid"}, + {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, + {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} ] } ] @@ -112,9 +180,9 @@ jobs: - name: Verify data run: | sudo apt-get update && sudo apt-get install -y mysql-client - COUNT=$(mysql -h 127.0.0.1 -u root -ptestpass testdb -N -s -e "SELECT COUNT(*) FROM users;") - echo "Found $COUNT rows" - [ "$COUNT" -eq 10 ] && echo "✅ MySQL test passed" + COUNT=$(mysql -h 127.0.0.1 -u root -ptestpass testdb -N -s -e "SELECT COUNT(*) FROM comprehensive_test;") + echo "Found $COUNT rows in comprehensive_test" + [ "$COUNT" -eq 10 ] && echo "✅ MySQL test passed - all generators working!" test-mariadb: name: Test MariaDB @@ -163,21 +231,89 @@ jobs: }, "tables": [ { - "name": "users", + "name": "comprehensive_test", "columns": [ {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "latitude", "type": "float"}, + {"name": "longitude", "type": "float"}, + {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, + {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, + {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, + {"name": "date_field", "type": "date"}, + {"name": "past_date", "type": "date"}, + {"name": "future_date", "type": "date"}, + {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "paragraph", "type": "text"}, + {"name": "random_int", "type": "integer"}, + {"name": "salary", "type": "float"}, + {"name": "price", "type": "float"}, + {"name": "is_active", "type": "boolean"}, + {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} ] } ], "populate": [ { - "name": "users", + "name": "comprehensive_test", "count": 10, "fields": [ + {"name": "first_name", "generator": "first_name"}, + {"name": "last_name", "generator": "last_name"}, + {"name": "full_name", "generator": "name"}, {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"} + {"name": "email", "generator": "email"}, + {"name": "password", "generator": "password"}, + {"name": "gender", "generator": "gender"}, + {"name": "address", "generator": "address"}, + {"name": "street_address", "generator": "street_address"}, + {"name": "city", "generator": "city"}, + {"name": "state", "generator": "state"}, + {"name": "country", "generator": "country"}, + {"name": "postcode", "generator": "postcode"}, + {"name": "latitude", "generator": "latitude"}, + {"name": "longitude", "generator": "longitude"}, + {"name": "company", "generator": "company"}, + {"name": "job_title", "generator": "job"}, + {"name": "phone", "generator": "phone_number"}, + {"name": "url", "generator": "url"}, + {"name": "domain", "generator": "domain_name"}, + {"name": "ipv4", "generator": "ipv4"}, + {"name": "ipv6", "generator": "ipv6"}, + {"name": "mac_address", "generator": "mac_address"}, + {"name": "date_field", "generator": "date"}, + {"name": "past_date", "generator": "past_date"}, + {"name": "future_date", "generator": "future_date"}, + {"name": "word", "generator": "word"}, + {"name": "sentence", "generator": "sentence"}, + {"name": "paragraph", "generator": "paragraph"}, + {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, + {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, + {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, + {"name": "is_active", "generator": "boolean"}, + {"name": "uuid", "generator": "uuid"}, + {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, + {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} ] } ] @@ -192,9 +328,9 @@ jobs: - name: Verify data run: | sudo apt-get update && sudo apt-get install -y mysql-client - COUNT=$(mysql -h 127.0.0.1 -u root -ptestpass testdb -N -s -e "SELECT COUNT(*) FROM users;") - echo "Found $COUNT rows" - [ "$COUNT" -eq 10 ] && echo "✅ MariaDB test passed" + COUNT=$(mysql -h 127.0.0.1 -u root -ptestpass testdb -N -s -e "SELECT COUNT(*) FROM comprehensive_test;") + echo "Found $COUNT rows in comprehensive_test" + [ "$COUNT" -eq 10 ] && echo "✅ MariaDB test passed - all generators working!" test-postgres: name: Test PostgreSQL @@ -243,21 +379,89 @@ jobs: }, "tables": [ { - "name": "users", + "name": "comprehensive_test", "columns": [ {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "latitude", "type": "float"}, + {"name": "longitude", "type": "float"}, + {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, + {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, + {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, + {"name": "date_field", "type": "date"}, + {"name": "past_date", "type": "date"}, + {"name": "future_date", "type": "date"}, + {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "paragraph", "type": "text"}, + {"name": "random_int", "type": "integer"}, + {"name": "salary", "type": "float"}, + {"name": "price", "type": "float"}, + {"name": "is_active", "type": "boolean"}, + {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} ] } ], "populate": [ { - "name": "users", + "name": "comprehensive_test", "count": 10, "fields": [ + {"name": "first_name", "generator": "first_name"}, + {"name": "last_name", "generator": "last_name"}, + {"name": "full_name", "generator": "name"}, {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"} + {"name": "email", "generator": "email"}, + {"name": "password", "generator": "password"}, + {"name": "gender", "generator": "gender"}, + {"name": "address", "generator": "address"}, + {"name": "street_address", "generator": "street_address"}, + {"name": "city", "generator": "city"}, + {"name": "state", "generator": "state"}, + {"name": "country", "generator": "country"}, + {"name": "postcode", "generator": "postcode"}, + {"name": "latitude", "generator": "latitude"}, + {"name": "longitude", "generator": "longitude"}, + {"name": "company", "generator": "company"}, + {"name": "job_title", "generator": "job"}, + {"name": "phone", "generator": "phone_number"}, + {"name": "url", "generator": "url"}, + {"name": "domain", "generator": "domain_name"}, + {"name": "ipv4", "generator": "ipv4"}, + {"name": "ipv6", "generator": "ipv6"}, + {"name": "mac_address", "generator": "mac_address"}, + {"name": "date_field", "generator": "date"}, + {"name": "past_date", "generator": "past_date"}, + {"name": "future_date", "generator": "future_date"}, + {"name": "word", "generator": "word"}, + {"name": "sentence", "generator": "sentence"}, + {"name": "paragraph", "generator": "paragraph"}, + {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, + {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, + {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, + {"name": "is_active", "generator": "boolean"}, + {"name": "uuid", "generator": "uuid"}, + {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, + {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} ] } ] @@ -272,9 +476,9 @@ jobs: - name: Verify data run: | sudo apt-get update && sudo apt-get install -y postgresql-client - COUNT=$(PGPASSWORD=testpass psql -h localhost -U postgres testdb -t -c "SELECT COUNT(*) FROM users;" | xargs) - echo "Found $COUNT rows" - [ "$COUNT" -eq 10 ] && echo "✅ PostgreSQL test passed" + COUNT=$(PGPASSWORD=testpass psql -h localhost -U postgres testdb -t -c "SELECT COUNT(*) FROM comprehensive_test;" | xargs) + echo "Found $COUNT rows in comprehensive_test" + [ "$COUNT" -eq 10 ] && echo "✅ PostgreSQL test passed - all generators working!" test-sqlite: name: Test SQLite @@ -306,21 +510,89 @@ jobs: }, "tables": [ { - "name": "users", + "name": "comprehensive_test", "columns": [ {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "latitude", "type": "float"}, + {"name": "longitude", "type": "float"}, + {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, + {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, + {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, + {"name": "date_field", "type": "date"}, + {"name": "past_date", "type": "date"}, + {"name": "future_date", "type": "date"}, + {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "paragraph", "type": "text"}, + {"name": "random_int", "type": "integer"}, + {"name": "salary", "type": "float"}, + {"name": "price", "type": "float"}, + {"name": "is_active", "type": "boolean"}, + {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} ] } ], "populate": [ { - "name": "users", + "name": "comprehensive_test", "count": 10, "fields": [ + {"name": "first_name", "generator": "first_name"}, + {"name": "last_name", "generator": "last_name"}, + {"name": "full_name", "generator": "name"}, {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"} + {"name": "email", "generator": "email"}, + {"name": "password", "generator": "password"}, + {"name": "gender", "generator": "gender"}, + {"name": "address", "generator": "address"}, + {"name": "street_address", "generator": "street_address"}, + {"name": "city", "generator": "city"}, + {"name": "state", "generator": "state"}, + {"name": "country", "generator": "country"}, + {"name": "postcode", "generator": "postcode"}, + {"name": "latitude", "generator": "latitude"}, + {"name": "longitude", "generator": "longitude"}, + {"name": "company", "generator": "company"}, + {"name": "job_title", "generator": "job"}, + {"name": "phone", "generator": "phone_number"}, + {"name": "url", "generator": "url"}, + {"name": "domain", "generator": "domain_name"}, + {"name": "ipv4", "generator": "ipv4"}, + {"name": "ipv6", "generator": "ipv6"}, + {"name": "mac_address", "generator": "mac_address"}, + {"name": "date_field", "generator": "date"}, + {"name": "past_date", "generator": "past_date"}, + {"name": "future_date", "generator": "future_date"}, + {"name": "word", "generator": "word"}, + {"name": "sentence", "generator": "sentence"}, + {"name": "paragraph", "generator": "paragraph"}, + {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, + {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, + {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, + {"name": "is_active", "generator": "boolean"}, + {"name": "uuid", "generator": "uuid"}, + {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, + {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} ] } ] @@ -335,9 +607,9 @@ jobs: - name: Verify data run: | sudo apt-get update && sudo apt-get install -y sqlite3 - COUNT=$(sqlite3 test.db "SELECT COUNT(*) FROM users;") - echo "Found $COUNT rows" - [ "$COUNT" -eq 10 ] && echo "✅ SQLite test passed" + COUNT=$(sqlite3 test.db "SELECT COUNT(*) FROM comprehensive_test;") + echo "Found $COUNT rows in comprehensive_test" + [ "$COUNT" -eq 10 ] && echo "✅ SQLite test passed - all generators working!" test-mssql: name: Test MS SQL Server @@ -391,21 +663,89 @@ jobs: }, "tables": [ { - "name": "users", + "name": "comprehensive_test", "columns": [ {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "latitude", "type": "float"}, + {"name": "longitude", "type": "float"}, + {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, + {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, + {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, + {"name": "date_field", "type": "date"}, + {"name": "past_date", "type": "date"}, + {"name": "future_date", "type": "date"}, + {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "paragraph", "type": "text"}, + {"name": "random_int", "type": "integer"}, + {"name": "salary", "type": "float"}, + {"name": "price", "type": "float"}, + {"name": "is_active", "type": "boolean"}, + {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} ] } ], "populate": [ { - "name": "users", + "name": "comprehensive_test", "count": 10, "fields": [ + {"name": "first_name", "generator": "first_name"}, + {"name": "last_name", "generator": "last_name"}, + {"name": "full_name", "generator": "name"}, {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"} + {"name": "email", "generator": "email"}, + {"name": "password", "generator": "password"}, + {"name": "gender", "generator": "gender"}, + {"name": "address", "generator": "address"}, + {"name": "street_address", "generator": "street_address"}, + {"name": "city", "generator": "city"}, + {"name": "state", "generator": "state"}, + {"name": "country", "generator": "country"}, + {"name": "postcode", "generator": "postcode"}, + {"name": "latitude", "generator": "latitude"}, + {"name": "longitude", "generator": "longitude"}, + {"name": "company", "generator": "company"}, + {"name": "job_title", "generator": "job"}, + {"name": "phone", "generator": "phone_number"}, + {"name": "url", "generator": "url"}, + {"name": "domain", "generator": "domain_name"}, + {"name": "ipv4", "generator": "ipv4"}, + {"name": "ipv6", "generator": "ipv6"}, + {"name": "mac_address", "generator": "mac_address"}, + {"name": "date_field", "generator": "date"}, + {"name": "past_date", "generator": "past_date"}, + {"name": "future_date", "generator": "future_date"}, + {"name": "word", "generator": "word"}, + {"name": "sentence", "generator": "sentence"}, + {"name": "paragraph", "generator": "paragraph"}, + {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, + {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, + {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, + {"name": "is_active", "generator": "boolean"}, + {"name": "uuid", "generator": "uuid"}, + {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, + {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} ] } ] @@ -421,9 +761,9 @@ jobs: run: | COUNT=$(docker exec $(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) \ /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' -d testdb -h -1 -C \ - -Q "SET NOCOUNT ON; SELECT COUNT(*) FROM users;" | xargs) - echo "Found $COUNT rows" - [ "$COUNT" -eq 10 ] && echo "✅ MSSQL test passed" + -Q "SET NOCOUNT ON; SELECT COUNT(*) FROM comprehensive_test;" | xargs) + echo "Found $COUNT rows in comprehensive_test" + [ "$COUNT" -eq 10 ] && echo "✅ MSSQL test passed - all generators working!" test-cockroachdb: name: Test CockroachDB @@ -470,21 +810,89 @@ jobs: }, "tables": [ { - "name": "users", + "name": "comprehensive_test", "columns": [ {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}, "options": {"nullable": false}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}, "options": {"nullable": false}} + {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "latitude", "type": "float"}, + {"name": "longitude", "type": "float"}, + {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, + {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, + {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, + {"name": "date_field", "type": "date"}, + {"name": "past_date", "type": "date"}, + {"name": "future_date", "type": "date"}, + {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "paragraph", "type": "text"}, + {"name": "random_int", "type": "integer"}, + {"name": "salary", "type": "float"}, + {"name": "price", "type": "float"}, + {"name": "is_active", "type": "boolean"}, + {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} ] } ], "populate": [ { - "name": "users", + "name": "comprehensive_test", "count": 10, "fields": [ + {"name": "first_name", "generator": "first_name"}, + {"name": "last_name", "generator": "last_name"}, + {"name": "full_name", "generator": "name"}, {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"} + {"name": "email", "generator": "email"}, + {"name": "password", "generator": "password"}, + {"name": "gender", "generator": "gender"}, + {"name": "address", "generator": "address"}, + {"name": "street_address", "generator": "street_address"}, + {"name": "city", "generator": "city"}, + {"name": "state", "generator": "state"}, + {"name": "country", "generator": "country"}, + {"name": "postcode", "generator": "postcode"}, + {"name": "latitude", "generator": "latitude"}, + {"name": "longitude", "generator": "longitude"}, + {"name": "company", "generator": "company"}, + {"name": "job_title", "generator": "job"}, + {"name": "phone", "generator": "phone_number"}, + {"name": "url", "generator": "url"}, + {"name": "domain", "generator": "domain_name"}, + {"name": "ipv4", "generator": "ipv4"}, + {"name": "ipv6", "generator": "ipv6"}, + {"name": "mac_address", "generator": "mac_address"}, + {"name": "date_field", "generator": "date"}, + {"name": "past_date", "generator": "past_date"}, + {"name": "future_date", "generator": "future_date"}, + {"name": "word", "generator": "word"}, + {"name": "sentence", "generator": "sentence"}, + {"name": "paragraph", "generator": "paragraph"}, + {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, + {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, + {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, + {"name": "is_active", "generator": "boolean"}, + {"name": "uuid", "generator": "uuid"}, + {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, + {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} ] } ] @@ -498,9 +906,9 @@ jobs: - name: Verify data run: | - COUNT=$(PGPASSWORD='' psql -h localhost -p 26257 -U root testdb -t -c "SELECT COUNT(*) FROM users;" | xargs) - echo "Found $COUNT rows" - [ "$COUNT" -eq 10 ] && echo "✅ CockroachDB test passed" + COUNT=$(PGPASSWORD='' psql -h localhost -p 26257 -U root testdb -t -c "SELECT COUNT(*) FROM comprehensive_test;" | xargs) + echo "Found $COUNT rows in comprehensive_test" + [ "$COUNT" -eq 10 ] && echo "✅ CockroachDB test passed - all generators working!" summary: name: Test Summary From ae793d0e1e3d9fa156d9cad3489a9ddc96b5b1c8 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 11:11:40 +0530 Subject: [PATCH 14/19] feat: add template generator for custom data patterns with modifiers and examples --- .gitignore | 9 +- CHANGELOG.md | 8 ++ README.md | 31 ++++++- golang/generator.go | 200 ++++++++++++++++++++++++++++++++++++++++++++ golang/go.mod | 3 +- golang/go.sum | 2 + 6 files changed, 249 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7a455ae..f734b27 100644 --- a/.gitignore +++ b/.gitignore @@ -545,5 +545,10 @@ bin/ **/bin/ .vercel/ -PUBLISHING_CHECKLIST.md -GO_*.md +# Documentation - ignore by default, add manually +*.md +!README.md +!CHANGELOG.md +!CONTRIBUTING.md +!LICENSE.md +!CODE_OF_CONDUCT.md diff --git a/CHANGELOG.md b/CHANGELOG.md index dd0538a..b48ab1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Template Generator**: Custom generator for creating data patterns using templates + - Syntax: `{{generator|modifier1|modifier2}}` + - Supports all built-in generators (name, email, word, uuid, random_int, etc.) + - Modifiers: `upper`, `lower`, `title`, `trim`, `truncate(n)` + - Example: `"{{word|upper|truncate(3)}}-{{random_int(1000,9999)}}"` → `PRD-4821` + - Perfect for SKUs, employee IDs, license plates, order numbers + - See `docs/TEMPLATE_EXAMPLES.md` for comprehensive examples + - Enables custom data formats without writing code - **Interactive Schema Generator**: New `-g` / `--generate` CLI flag for creating schemas - Built directly into fakestack CLI (no separate script needed) - 10 pre-built templates (Users, Employees, Products, Orders, Customers, Blog Posts, Inventory, Transactions, Students, Tasks) diff --git a/README.md b/README.md index 378de6b..fc8e756 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ Generate databases from JSON schemas with realistic fake data. **10-50x faster** - 🚀 **Schema-Driven** - Define tables and data in simple JSON format - ⚡ **High Performance** - Go core delivers 10-50x speed improvement - 💡 **Realistic Data** - 50+ generators for names, emails, addresses, dates, and more -- 🗄️ **Multi-Database** - Works with SQLite, MySQL, and PostgreSQL +- 🎨 **Custom Patterns** - Template generator for custom data formats (SKUs, IDs, codes) +- 🗄️ **Multi-Database** - Works with SQLite, MySQL, PostgreSQL, MariaDB, MSSQL, CockroachDB - 🎯 **Simple API** - Easy CLI and programmatic usage - 🌍 **Cross-Platform** - Linux, macOS, Windows (amd64 & arm64) - 📦 **Multi-Ecosystem** - Available on PyPI, npm, and Homebrew @@ -303,6 +304,33 @@ Create a `schema.json` file defining your database structure: } ``` +## 🎨 Custom Data Patterns + +Create custom data formats using the **template generator**: + +```json +{ + "name": "sku", + "generator": "template", + "args": { + "pattern": "{{word|upper|truncate(3)}}-{{random_int(1000,9999)}}" + } +} +``` +**Output**: `PRD-4821`, `INV-9234`, `STO-1456` + +**More Examples:** +```json +{"pattern": "EMP-{{random_int(10000,99999)}}"} // EMP-45678 +{"pattern": "{{uuid|upper|truncate(8)}}"} // A1B2C3D4 +{"pattern": "{{city}}, {{state}} {{postcode}}"} // San Francisco, CA 94102 +``` + +**Supported Modifiers**: `upper`, `lower`, `title`, `trim`, `truncate(n)` + +📚 **[Template Examples](docs/TEMPLATE_EXAMPLES.md)** - Comprehensive examples +📖 **[Custom Generators Guide](docs/CUSTOM_GENERATORS.md)** - Full guide with advanced patterns + ## 📚 Documentation 📖 **[Full Documentation on ReadTheDocs](https://fake-stack.readthedocs.io/)** - Complete documentation with examples and tutorials @@ -310,6 +338,7 @@ Create a `schema.json` file defining your database structure: - **[Getting Started](docs/getting-started.md)** - Installation and basic usage - **[Schema Reference](docs/schema-reference.md)** - Complete schema documentation - **[Data Generators](docs/generators.md)** - All available data generators +- **[Custom Generators](docs/CUSTOM_GENERATORS.md)** - Template generator guide - **[Database Support](docs/databases.md)** - Database-specific configuration - **[API Reference](docs/api-reference.md)** - Python and Node.js APIs - **[Examples](docs/examples.md)** - Real-world examples diff --git a/golang/generator.go b/golang/generator.go index e15d39f..67be662 100644 --- a/golang/generator.go +++ b/golang/generator.go @@ -5,6 +5,8 @@ import ( "strings" "github.com/brianvoe/gofakeit/v7" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) // Generator handles fake data generation @@ -159,6 +161,8 @@ func (g *Generator) Generate(field PopulateField, commons map[string]interface{} return g.uniqueItem(field.Name, args) case "db_random_item": return g.dbRandomItem(args) + case "template": + return g.generateFromTemplate(args, commons) default: return nil, fmt.Errorf("unknown generator: %s", generator) @@ -289,3 +293,199 @@ func (g *Generator) dbRandomItem(args interface{}) (interface{}, error) { func (g *Generator) ClearUniques() { g.uniques = make(map[string]map[interface{}]bool) } + +// generateFromTemplate generates value from a template pattern +func (g *Generator) generateFromTemplate(args interface{}, commons map[string]interface{}) (interface{}, error) { + argsMap, ok := args.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("template generator requires args object with 'pattern' field") + } + + pattern, ok := argsMap["pattern"].(string) + if !ok { + return nil, fmt.Errorf("template generator requires 'pattern' string") + } + + return g.parseTemplate(pattern, commons) +} + +// parseTemplate parses and executes template pattern +func (g *Generator) parseTemplate(pattern string, commons map[string]interface{}) (string, error) { + result := pattern + + // Find all {{...}} placeholders + for { + start := strings.Index(result, "{{") + if start == -1 { + break + } + + end := strings.Index(result[start:], "}}") + if end == -1{ + return "", fmt.Errorf("unclosed template placeholder in: %s", pattern) + } + end += start + + // Extract placeholder content + placeholder := result[start+2 : end] + + // Parse placeholder: generator|modifier1|modifier2 + parts := strings.Split(placeholder, "|") + generatorExpr := strings.TrimSpace(parts[0]) + modifiers := parts[1:] + + // Generate value + value, err := g.executeTemplateGenerator(generatorExpr, commons) + if err != nil { + return "", fmt.Errorf("failed to execute generator '%s': %w", generatorExpr, err) + } + + // Apply modifiers + valueStr := fmt.Sprintf("%v", value) + for _, modifier := range modifiers { + valueStr = g.applyModifier(valueStr, strings.TrimSpace(modifier)) + } + + // Replace placeholder with generated value + result = result[:start] + valueStr + result[end+2:] + } + + return result, nil +} + +// executeTemplateGenerator executes a generator expression +func (g *Generator) executeTemplateGenerator(expr string, commons map[string]interface{}) (interface{}, error) { + // Parse generator name and arguments + // Format: generator_name or generator_name(arg1,arg2) + + parenIndex := strings.Index(expr, "(") + if parenIndex == -1 { + // No arguments, simple generator + return g.executeSimpleGenerator(expr) + } + + // Has arguments + generatorName := strings.TrimSpace(expr[:parenIndex]) + argsStr := expr[parenIndex+1:] + + // Remove closing parenthesis + if !strings.HasSuffix(argsStr, ")") { + return nil, fmt.Errorf("missing closing parenthesis in: %s", expr) + } + argsStr = argsStr[:len(argsStr)-1] + + // Parse arguments + args := g.parseTemplateArgs(argsStr) + + return g.executeGeneratorWithArgs(generatorName, args) +} + +// executeSimpleGenerator executes a generator without arguments +func (g *Generator) executeSimpleGenerator(name string) (interface{}, error) { + switch name { + case "name": + return g.fake.Name(), nil + case "first_name": + return g.fake.FirstName(), nil + case "last_name": + return g.fake.LastName(), nil + case "user_name", "username": + return g.fake.Username(), nil + case "email": + return g.fake.Email(), nil + case "word": + return g.fake.Word(), nil + case "sentence": + return g.fake.Sentence(10), nil + case "city": + return g.fake.Address().City, nil + case "state": + return g.fake.Address().State, nil + case "country": + return g.fake.Address().Country, nil + case "postcode", "zip_code": + return g.fake.Address().Zip, nil + case "company": + return g.fake.Company(), nil + case "uuid": + return g.fake.UUID(), nil + case "phone": + return g.fake.Phone(), nil + case "date": + return g.fake.Date(), nil + default: + return nil, fmt.Errorf("unknown generator: %s", name) + } +} + +// executeGeneratorWithArgs executes a generator with arguments +func (g *Generator) executeGeneratorWithArgs(name string, args []string) (interface{}, error) { + switch name { + case "random_int": + if len(args) != 2 { + return nil, fmt.Errorf("random_int requires 2 arguments: min, max") + } + min, max := 0, 100 + fmt.Sscanf(args[0], "%d", &min) + fmt.Sscanf(args[1], "%d", &max) + return g.fake.IntRange(min, max), nil + case "truncate": + // Special case: handled as modifier + return nil, fmt.Errorf("truncate should be used as modifier, not generator") + default: + return g.executeSimpleGenerator(name) + } +} + +// parseTemplateArgs parses comma-separated arguments +func (g *Generator) parseTemplateArgs(argsStr string) []string { + if argsStr == "" { + return []string{} + } + + parts := strings.Split(argsStr, ",") + args := make([]string, len(parts)) + for i, part := range parts { + args[i] = strings.TrimSpace(part) + } + return args +} + +// applyModifier applies a modifier to a string value +func (g *Generator) applyModifier(value, modifier string) string { + // Check if modifier has arguments: modifier(arg) + parenIndex := strings.Index(modifier, "(") + if parenIndex != -1 { + modName := modifier[:parenIndex] + argStr := modifier[parenIndex+1 : len(modifier)-1] + + switch modName { + case "truncate": + var length int + fmt.Sscanf(argStr, "%d", &length) + if length > 0 && len(value) > length { + return value[:length] + } + return value + case "format": + // Date formatting - simplified + return value + } + return value + } + + // Simple modifiers + switch modifier { + case "upper": + return strings.ToUpper(value) + case "lower": + return strings.ToLower(value) + case "title": + caser := cases.Title(language.English) + return caser.String(strings.ToLower(value)) + case "trim": + return strings.TrimSpace(value) + default: + return value + } +} diff --git a/golang/go.mod b/golang/go.mod index c654835..23b5c5f 100644 --- a/golang/go.mod +++ b/golang/go.mod @@ -1,6 +1,6 @@ module github.com/0xdps/fake-stack/core -go 1.22 +go 1.24.0 require ( github.com/brianvoe/gofakeit/v7 v7.1.2 @@ -15,4 +15,5 @@ require ( github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/text v0.31.0 // indirect ) diff --git a/golang/go.sum b/golang/go.sum index 67636db..4a4f92b 100644 --- a/golang/go.sum +++ b/golang/go.sum @@ -40,6 +40,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From d57659989a35d843feb8fd41d6909a80de332a17 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 11:23:30 +0530 Subject: [PATCH 15/19] feat: expand data generation capabilities with 80+ new generators across multiple categories --- CHANGELOG.md | 27 ++++++- README.md | 2 +- golang/generator.go | 174 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48ab1c..8eb0cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,10 +24,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Automatic row count suggestions per template - Usage: `fakestack -g .` or `fakestack -g my-schema.json` - Implementation in `golang/templates.go` for clean code organization -- **New Generators**: Added `integer`, `float`, and `decimal` generators with min/max range support - - `integer` / `random_int` - Generate integers with configurable min/max range - - `float` / `decimal` - Generate floating-point numbers with configurable min/max range - - Backward compatible with existing `random_int` generator +- **New Generators**: Added 80+ generators across 12 categories for comprehensive test data generation + - **Financial/Payment** (8): `credit_card`, `credit_card_type`, `credit_card_cvv`, `credit_card_exp`, `currency`, `currency_long`, `bitcoin_address`, `bitcoin_private_key` + - **Time & Timestamps** (7): `timestamp`, `time`, `year`, `month`, `month_string`, `weekday`, `timezone` + - **Localization** (4): `country_code`, `language`, `language_abbr`, `locale` + - **Product/E-commerce** (8): `color`, `hex_color`, `safe_color`, `product_name`, `product_category`, `product_description`, `product_feature`, `price` + - **Files & Media** (4): `filename`, `file_extension`, `mime_type`, `image_url` + - **User Agent & Browser** (5): `user_agent`, `chrome_user_agent`, `firefox_user_agent`, `safari_user_agent`, `opera_user_agent` + - **Books & Media** (5): `book_title`, `book_author`, `book_genre`, `movie_name`, `movie_genre` + - **Animals & Nature** (7): `animal`, `animal_type`, `pet_name`, `cat`, `dog`, `bird`, `farm_animal` + - **Food** (8): `fruit`, `vegetable`, `breakfast`, `lunch`, `dinner`, `snack`, `dessert`, `drink` + - **Vehicle/Transportation** (5): `car_maker`, `car_model`, `car_type`, `car_fuel_type`, `car_transmission_type` + - **Identifiers** (4): `ssn`, `ein`, `iban`, `routing_number` + - **Text Types** (6): `emoji`, `emoji_description`, `emoji_category`, `quote`, `phrase`, `question` + - **App & Software** (3): `app_name`, `app_version`, `app_author` + - **Range Support**: Added `integer`, `float`, and `decimal` generators with min/max range support + - `integer` / `random_int` - Generate integers with configurable min/max range + - `float` / `decimal` - Generate floating-point numbers with configurable min/max range + - Backward compatible with existing `random_int` generator + - Total generators: 116+ covering most common data generation needs - **Database Support**: Added support for 3 additional database systems - MariaDB - MySQL-compatible database with enhanced features - MS SQL Server - Microsoft's enterprise database with IDENTITY syntax support @@ -55,6 +70,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated `CREATE TABLE` to use `IF OBJECT_ID` syntax - Updated `DROP TABLE` to use `IF OBJECT_ID` syntax - Fixed sqlcmd path in workflow to `/opt/mssql-tools18/bin/sqlcmd` with `-C` flag +- **Deprecation Fix**: Replaced deprecated `strings.Title()` with Unicode-compliant `cases.Title()` + - Added dependency: `golang.org/x/text v0.31.0` + - Fixed deprecation warning in Go 1.24.0 + - Improved Unicode handling for title casing ### Technical Details - Total supported databases: **6** (SQLite, MySQL, PostgreSQL, MariaDB, MSSQL, CockroachDB) diff --git a/README.md b/README.md index fc8e756..8012d28 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Generate databases from JSON schemas with realistic fake data. **10-50x faster** - 🚀 **Schema-Driven** - Define tables and data in simple JSON format - ⚡ **High Performance** - Go core delivers 10-50x speed improvement -- 💡 **Realistic Data** - 50+ generators for names, emails, addresses, dates, and more +- 💡 **Realistic Data** - 116+ generators covering financial, localization, products, animals, food, and more - 🎨 **Custom Patterns** - Template generator for custom data formats (SKUs, IDs, codes) - 🗄️ **Multi-Database** - Works with SQLite, MySQL, PostgreSQL, MariaDB, MSSQL, CockroachDB - 🎯 **Simple API** - Easy CLI and programmatic usage diff --git a/golang/generator.go b/golang/generator.go index 67be662..6895ad3 100644 --- a/golang/generator.go +++ b/golang/generator.go @@ -150,6 +150,180 @@ func (g *Generator) Generate(field PopulateField, commons map[string]interface{} case "uuid": return g.fake.UUID(), nil + // Financial/Payment + case "credit_card": + return g.fake.CreditCardNumber(nil), nil + case "credit_card_type": + return g.fake.CreditCardType(), nil + case "credit_card_cvv": + return g.fake.CreditCardCvv(), nil + case "credit_card_exp": + return g.fake.CreditCardExp(), nil + case "currency": + return g.fake.CurrencyShort(), nil + case "currency_long": + return g.fake.CurrencyLong(), nil + case "bitcoin_address": + return g.fake.BitcoinAddress(), nil + case "bitcoin_private_key": + return g.fake.BitcoinPrivateKey(), nil + + // Time & Timestamps + case "timestamp": + return g.fake.Date().Unix(), nil + case "time": + return g.fake.Date().Format("15:04:05"), nil + case "year": + return g.fake.Year(), nil + case "month": + return g.fake.Month(), nil + case "month_string": + return g.fake.MonthString(), nil + case "weekday": + return g.fake.WeekDay(), nil + case "timezone": + return g.fake.TimeZone(), nil + + // Localization + case "country_code": + return g.fake.CountryAbr(), nil + case "language": + return g.fake.Language(), nil + case "language_abbr": + return g.fake.LanguageAbbreviation(), nil + case "locale": + return g.fake.Language() + "_" + g.fake.CountryAbr(), nil + + // Product/E-commerce + case "color": + return g.fake.Color(), nil + case "hex_color": + return g.fake.HexColor(), nil + case "safe_color": + return g.fake.SafeColor(), nil + case "product_name": + return g.fake.ProductName(), nil + case "product_category": + return g.fake.ProductCategory(), nil + case "product_description": + return g.fake.ProductDescription(), nil + case "product_feature": + return g.fake.ProductFeature(), nil + case "price": + return g.fake.Price(1.0, 1000.0), nil + + // Files & Media + case "filename": + return g.fake.Word() + "." + g.fake.FileExtension(), nil + case "file_extension": + return g.fake.FileExtension(), nil + case "mime_type": + return g.fake.FileMimeType(), nil + case "image_url": + return fmt.Sprintf("https://picsum.photos/%d/%d", 400, 300), nil + + // User Agent & Browser + case "user_agent": + return g.fake.UserAgent(), nil + case "chrome_user_agent": + return g.fake.ChromeUserAgent(), nil + case "firefox_user_agent": + return g.fake.FirefoxUserAgent(), nil + case "safari_user_agent": + return g.fake.SafariUserAgent(), nil + case "opera_user_agent": + return g.fake.OperaUserAgent(), nil + + // Books & Media + case "book_title": + return g.fake.BookTitle(), nil + case "book_author": + return g.fake.BookAuthor(), nil + case "book_genre": + return g.fake.BookGenre(), nil + case "movie_name": + return g.fake.MovieName(), nil + case "movie_genre": + return g.fake.MovieGenre(), nil + + // Animals & Nature + case "animal": + return g.fake.Animal(), nil + case "animal_type": + return g.fake.AnimalType(), nil + case "pet_name": + return g.fake.PetName(), nil + case "cat": + return g.fake.Cat(), nil + case "dog": + return g.fake.Dog(), nil + case "bird": + return g.fake.Bird(), nil + case "farm_animal": + return g.fake.FarmAnimal(), nil + + // Food + case "fruit": + return g.fake.Fruit(), nil + case "vegetable": + return g.fake.Vegetable(), nil + case "breakfast": + return g.fake.Breakfast(), nil + case "lunch": + return g.fake.Lunch(), nil + case "dinner": + return g.fake.Dinner(), nil + case "snack": + return g.fake.Snack(), nil + case "dessert": + return g.fake.Dessert(), nil + case "drink": + return g.fake.Drink(), nil + + // Vehicle/Transportation + case "car_maker": + return g.fake.CarMaker(), nil + case "car_model": + return g.fake.CarModel(), nil + case "car_type": + return g.fake.CarType(), nil + case "car_fuel_type": + return g.fake.CarFuelType(), nil + case "car_transmission_type": + return g.fake.CarTransmissionType(), nil + + // Identifiers + case "ssn": + return g.fake.SSN(), nil + case "ein": + return g.fake.Cusip(), nil + case "iban": + return g.fake.AchAccount(), nil + case "routing_number": + return g.fake.AchRouting(), nil + + // Additional Text Types + case "emoji": + return g.fake.Emoji(), nil + case "emoji_description": + return g.fake.EmojiDescription(), nil + case "emoji_category": + return g.fake.EmojiCategory(), nil + case "quote": + return g.fake.Quote(), nil + case "phrase": + return g.fake.Phrase(), nil + case "question": + return g.fake.Question(), nil + + // App & Software + case "app_name": + return g.fake.AppName(), nil + case "app_version": + return g.fake.AppVersion(), nil + case "app_author": + return g.fake.AppAuthor(), nil + // Custom generators case "person": return g.generatePerson() From 19324eb90b9dce0f4c292b03dc5b06739a064650 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 11:35:06 +0530 Subject: [PATCH 16/19] Add centralized test schema and workflow documentation - Introduced a centralized test schema in `test-schema.json` to standardize database integration tests across multiple databases. - Created a comprehensive README in `.github/workflows/README.md` detailing the test schema, its structure, and guidelines for adding new generators. - Ensured validation of generator fields to maintain consistency and reduce duplication in test setups. --- .github/workflows/README.md | 64 +++ .github/workflows/test-databases.yml | 640 +++------------------------ .github/workflows/test-schema.json | 184 ++++++++ 3 files changed, 319 insertions(+), 569 deletions(-) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/test-schema.json diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..c09528d --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,64 @@ +# Test Database Workflows + +## Centralized Test Schema + +The database integration tests use a centralized schema to ensure consistency across all supported databases. + +### Schema File + +**`test-schema.json`** - Contains the complete test schema with: +- **84 columns** covering all generator types +- **83 generator fields** (excluding auto-increment ID) +- All categories: Personal, Location, Network, Financial, Time, Localization, Product, Files, Media, Animals, Food, Vehicles, Identifiers, Text, and App data + +### How It Works + +Each database test job: +1. Merges the centralized schema with database-specific configuration using `jq` +2. Validates that exactly 83 generator fields are present +3. Creates and populates the test table +4. Verifies the row count + +### Benefits + +✅ **Single Source of Truth** - All generator fields defined once +✅ **Consistency** - Same tests across all 6 databases +✅ **Easy Updates** - Add new generators in one place +✅ **Validation** - Automatic field count verification +✅ **Reduced Duplication** - 71 lines vs 569 lines per database + +### Supported Databases + +1. **MySQL** 8.0 +2. **MariaDB** 10.11 +3. **PostgreSQL** 14 +4. **SQLite** 3.x +5. **MS SQL Server** 2022 +6. **CockroachDB** Latest + +### Adding New Generators + +To add a new generator to all database tests: + +1. Add the column to `test-schema.json` in the `tables[0].columns` array +2. Add the generator field to `test-schema.json` in the `populate[0].fields` array +3. Update the field count validation in `test-databases.yml` (increment the `83` in all 6 tests) + +Example: +```json +// In tables[0].columns: +{"name": "new_field", "type": {"name": "string", "args": {"length": 50}}} + +// In populate[0].fields: +{"name": "new_field", "generator": "new_generator"} +``` + +### Schema Validation + +Each test automatically validates: +```bash +FIELD_COUNT=$(jq '.populate[0].fields | length' test-schema.json) +[ "$FIELD_COUNT" -eq 83 ] || (echo "❌ Expected 83 fields, got $FIELD_COUNT" && exit 1) +``` + +This ensures no fields are accidentally omitted during schema merging. diff --git a/.github/workflows/test-databases.yml b/.github/workflows/test-databases.yml index 08e55db..7235d5d 100644 --- a/.github/workflows/test-databases.yml +++ b/.github/workflows/test-databases.yml @@ -72,105 +72,22 @@ jobs: - name: Create test schema run: | - cat > test-schema.json << 'EOF' - { - "database": { + # Merge centralized schema with database-specific config + jq -s '.[0] * {"database": .[1]}' \ + .github/workflows/test-schema.json \ + <(echo '{ "dbtype": "mysql", "username": "root", "password": "testpass", "host": "127.0.0.1:3306", "database": "testdb" - }, - "tables": [ - { - "name": "comprehensive_test", - "columns": [ - {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "latitude", "type": "float"}, - {"name": "longitude", "type": "float"}, - {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, - {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, - {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, - {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, - {"name": "date_field", "type": "date"}, - {"name": "past_date", "type": "date"}, - {"name": "future_date", "type": "date"}, - {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "paragraph", "type": "text"}, - {"name": "random_int", "type": "integer"}, - {"name": "salary", "type": "float"}, - {"name": "price", "type": "float"}, - {"name": "is_active", "type": "boolean"}, - {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, - {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} - ] - } - ], - "populate": [ - { - "name": "comprehensive_test", - "count": 10, - "fields": [ - {"name": "first_name", "generator": "first_name"}, - {"name": "last_name", "generator": "last_name"}, - {"name": "full_name", "generator": "name"}, - {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"}, - {"name": "password", "generator": "password"}, - {"name": "gender", "generator": "gender"}, - {"name": "address", "generator": "address"}, - {"name": "street_address", "generator": "street_address"}, - {"name": "city", "generator": "city"}, - {"name": "state", "generator": "state"}, - {"name": "country", "generator": "country"}, - {"name": "postcode", "generator": "postcode"}, - {"name": "latitude", "generator": "latitude"}, - {"name": "longitude", "generator": "longitude"}, - {"name": "company", "generator": "company"}, - {"name": "job_title", "generator": "job"}, - {"name": "phone", "generator": "phone_number"}, - {"name": "url", "generator": "url"}, - {"name": "domain", "generator": "domain_name"}, - {"name": "ipv4", "generator": "ipv4"}, - {"name": "ipv6", "generator": "ipv6"}, - {"name": "mac_address", "generator": "mac_address"}, - {"name": "date_field", "generator": "date"}, - {"name": "past_date", "generator": "past_date"}, - {"name": "future_date", "generator": "future_date"}, - {"name": "word", "generator": "word"}, - {"name": "sentence", "generator": "sentence"}, - {"name": "paragraph", "generator": "paragraph"}, - {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, - {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, - {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, - {"name": "is_active", "generator": "boolean"}, - {"name": "uuid", "generator": "uuid"}, - {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, - {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} - ] - } - ] - } - EOF + }') \ + > test-schema.json + + # Verify the schema has all expected fields + FIELD_COUNT=$(jq '.populate[0].fields | length' test-schema.json) + echo "Schema contains $FIELD_COUNT generator fields" + [ "$FIELD_COUNT" -eq 83 ] || (echo "❌ Expected 83 fields, got $FIELD_COUNT" && exit 1) - name: Test create and populate run: | @@ -220,105 +137,22 @@ jobs: - name: Create test schema run: | - cat > test-schema.json << 'EOF' - { - "database": { - "dbtype": "mariadb", + # Merge centralized schema with database-specific config + jq -s '.[0] * {"database": .[1]}' \ + .github/workflows/test-schema.json \ + <(echo '{ + "dbtype": "mysql", "username": "root", "password": "testpass", "host": "127.0.0.1:3306", "database": "testdb" - }, - "tables": [ - { - "name": "comprehensive_test", - "columns": [ - {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "latitude", "type": "float"}, - {"name": "longitude", "type": "float"}, - {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, - {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, - {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, - {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, - {"name": "date_field", "type": "date"}, - {"name": "past_date", "type": "date"}, - {"name": "future_date", "type": "date"}, - {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "paragraph", "type": "text"}, - {"name": "random_int", "type": "integer"}, - {"name": "salary", "type": "float"}, - {"name": "price", "type": "float"}, - {"name": "is_active", "type": "boolean"}, - {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, - {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} - ] - } - ], - "populate": [ - { - "name": "comprehensive_test", - "count": 10, - "fields": [ - {"name": "first_name", "generator": "first_name"}, - {"name": "last_name", "generator": "last_name"}, - {"name": "full_name", "generator": "name"}, - {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"}, - {"name": "password", "generator": "password"}, - {"name": "gender", "generator": "gender"}, - {"name": "address", "generator": "address"}, - {"name": "street_address", "generator": "street_address"}, - {"name": "city", "generator": "city"}, - {"name": "state", "generator": "state"}, - {"name": "country", "generator": "country"}, - {"name": "postcode", "generator": "postcode"}, - {"name": "latitude", "generator": "latitude"}, - {"name": "longitude", "generator": "longitude"}, - {"name": "company", "generator": "company"}, - {"name": "job_title", "generator": "job"}, - {"name": "phone", "generator": "phone_number"}, - {"name": "url", "generator": "url"}, - {"name": "domain", "generator": "domain_name"}, - {"name": "ipv4", "generator": "ipv4"}, - {"name": "ipv6", "generator": "ipv6"}, - {"name": "mac_address", "generator": "mac_address"}, - {"name": "date_field", "generator": "date"}, - {"name": "past_date", "generator": "past_date"}, - {"name": "future_date", "generator": "future_date"}, - {"name": "word", "generator": "word"}, - {"name": "sentence", "generator": "sentence"}, - {"name": "paragraph", "generator": "paragraph"}, - {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, - {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, - {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, - {"name": "is_active", "generator": "boolean"}, - {"name": "uuid", "generator": "uuid"}, - {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, - {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} - ] - } - ] - } - EOF + }') \ + > test-schema.json + + # Verify the schema has all expected fields + FIELD_COUNT=$(jq '.populate[0].fields | length' test-schema.json) + echo "Schema contains $FIELD_COUNT generator fields" + [ "$FIELD_COUNT" -eq 83 ] || (echo "❌ Expected 83 fields, got $FIELD_COUNT" && exit 1) - name: Test create and populate run: | @@ -368,105 +202,22 @@ jobs: - name: Create test schema run: | - cat > test-schema.json << 'EOF' - { - "database": { - "dbtype": "psql", + # Merge centralized schema with database-specific config + jq -s '.[0] * {"database": .[1]}' \ + .github/workflows/test-schema.json \ + <(echo '{ + "dbtype": "postgres", "username": "postgres", "password": "testpass", "host": "localhost:5432", "database": "testdb" - }, - "tables": [ - { - "name": "comprehensive_test", - "columns": [ - {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "latitude", "type": "float"}, - {"name": "longitude", "type": "float"}, - {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, - {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, - {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, - {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, - {"name": "date_field", "type": "date"}, - {"name": "past_date", "type": "date"}, - {"name": "future_date", "type": "date"}, - {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "paragraph", "type": "text"}, - {"name": "random_int", "type": "integer"}, - {"name": "salary", "type": "float"}, - {"name": "price", "type": "float"}, - {"name": "is_active", "type": "boolean"}, - {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, - {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} - ] - } - ], - "populate": [ - { - "name": "comprehensive_test", - "count": 10, - "fields": [ - {"name": "first_name", "generator": "first_name"}, - {"name": "last_name", "generator": "last_name"}, - {"name": "full_name", "generator": "name"}, - {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"}, - {"name": "password", "generator": "password"}, - {"name": "gender", "generator": "gender"}, - {"name": "address", "generator": "address"}, - {"name": "street_address", "generator": "street_address"}, - {"name": "city", "generator": "city"}, - {"name": "state", "generator": "state"}, - {"name": "country", "generator": "country"}, - {"name": "postcode", "generator": "postcode"}, - {"name": "latitude", "generator": "latitude"}, - {"name": "longitude", "generator": "longitude"}, - {"name": "company", "generator": "company"}, - {"name": "job_title", "generator": "job"}, - {"name": "phone", "generator": "phone_number"}, - {"name": "url", "generator": "url"}, - {"name": "domain", "generator": "domain_name"}, - {"name": "ipv4", "generator": "ipv4"}, - {"name": "ipv6", "generator": "ipv6"}, - {"name": "mac_address", "generator": "mac_address"}, - {"name": "date_field", "generator": "date"}, - {"name": "past_date", "generator": "past_date"}, - {"name": "future_date", "generator": "future_date"}, - {"name": "word", "generator": "word"}, - {"name": "sentence", "generator": "sentence"}, - {"name": "paragraph", "generator": "paragraph"}, - {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, - {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, - {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, - {"name": "is_active", "generator": "boolean"}, - {"name": "uuid", "generator": "uuid"}, - {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, - {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} - ] - } - ] - } - EOF + }') \ + > test-schema.json + + # Verify the schema has all expected fields + FIELD_COUNT=$(jq '.populate[0].fields | length' test-schema.json) + echo "Schema contains $FIELD_COUNT generator fields" + [ "$FIELD_COUNT" -eq 83 ] || (echo "❌ Expected 83 fields, got $FIELD_COUNT" && exit 1) - name: Test create and populate run: | @@ -502,102 +253,19 @@ jobs: - name: Create test schema run: | - cat > test-schema.json << 'EOF' - { - "database": { + # Merge centralized schema with database-specific config + jq -s '.[0] * {"database": .[1]}' \ + .github/workflows/test-schema.json \ + <(echo '{ "dbtype": "sqlite", - "database": "test.db" - }, - "tables": [ - { - "name": "comprehensive_test", - "columns": [ - {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "latitude", "type": "float"}, - {"name": "longitude", "type": "float"}, - {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, - {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, - {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, - {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, - {"name": "date_field", "type": "date"}, - {"name": "past_date", "type": "date"}, - {"name": "future_date", "type": "date"}, - {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "paragraph", "type": "text"}, - {"name": "random_int", "type": "integer"}, - {"name": "salary", "type": "float"}, - {"name": "price", "type": "float"}, - {"name": "is_active", "type": "boolean"}, - {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, - {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} - ] - } - ], - "populate": [ - { - "name": "comprehensive_test", - "count": 10, - "fields": [ - {"name": "first_name", "generator": "first_name"}, - {"name": "last_name", "generator": "last_name"}, - {"name": "full_name", "generator": "name"}, - {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"}, - {"name": "password", "generator": "password"}, - {"name": "gender", "generator": "gender"}, - {"name": "address", "generator": "address"}, - {"name": "street_address", "generator": "street_address"}, - {"name": "city", "generator": "city"}, - {"name": "state", "generator": "state"}, - {"name": "country", "generator": "country"}, - {"name": "postcode", "generator": "postcode"}, - {"name": "latitude", "generator": "latitude"}, - {"name": "longitude", "generator": "longitude"}, - {"name": "company", "generator": "company"}, - {"name": "job_title", "generator": "job"}, - {"name": "phone", "generator": "phone_number"}, - {"name": "url", "generator": "url"}, - {"name": "domain", "generator": "domain_name"}, - {"name": "ipv4", "generator": "ipv4"}, - {"name": "ipv6", "generator": "ipv6"}, - {"name": "mac_address", "generator": "mac_address"}, - {"name": "date_field", "generator": "date"}, - {"name": "past_date", "generator": "past_date"}, - {"name": "future_date", "generator": "future_date"}, - {"name": "word", "generator": "word"}, - {"name": "sentence", "generator": "sentence"}, - {"name": "paragraph", "generator": "paragraph"}, - {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, - {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, - {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, - {"name": "is_active", "generator": "boolean"}, - {"name": "uuid", "generator": "uuid"}, - {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, - {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} - ] - } - ] - } - EOF + "host": "test.db" + }') \ + > test-schema.json + + # Verify the schema has all expected fields + FIELD_COUNT=$(jq '.populate[0].fields | length' test-schema.json) + echo "Schema contains $FIELD_COUNT generator fields" + [ "$FIELD_COUNT" -eq 83 ] || (echo "❌ Expected 83 fields, got $FIELD_COUNT" && exit 1) - name: Test create and populate run: | @@ -652,105 +320,22 @@ jobs: - name: Create test schema run: | - cat > test-schema.json << 'EOF' - { - "database": { + # Merge centralized schema with database-specific config + jq -s '.[0] * {"database": .[1]}' \ + .github/workflows/test-schema.json \ + <(echo '{ "dbtype": "mssql", "username": "sa", - "password": "TestPass123!", + "password": "YourStrong@Passw0rd", "host": "localhost:1433", "database": "testdb" - }, - "tables": [ - { - "name": "comprehensive_test", - "columns": [ - {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "latitude", "type": "float"}, - {"name": "longitude", "type": "float"}, - {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, - {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, - {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, - {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, - {"name": "date_field", "type": "date"}, - {"name": "past_date", "type": "date"}, - {"name": "future_date", "type": "date"}, - {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "paragraph", "type": "text"}, - {"name": "random_int", "type": "integer"}, - {"name": "salary", "type": "float"}, - {"name": "price", "type": "float"}, - {"name": "is_active", "type": "boolean"}, - {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, - {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} - ] - } - ], - "populate": [ - { - "name": "comprehensive_test", - "count": 10, - "fields": [ - {"name": "first_name", "generator": "first_name"}, - {"name": "last_name", "generator": "last_name"}, - {"name": "full_name", "generator": "name"}, - {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"}, - {"name": "password", "generator": "password"}, - {"name": "gender", "generator": "gender"}, - {"name": "address", "generator": "address"}, - {"name": "street_address", "generator": "street_address"}, - {"name": "city", "generator": "city"}, - {"name": "state", "generator": "state"}, - {"name": "country", "generator": "country"}, - {"name": "postcode", "generator": "postcode"}, - {"name": "latitude", "generator": "latitude"}, - {"name": "longitude", "generator": "longitude"}, - {"name": "company", "generator": "company"}, - {"name": "job_title", "generator": "job"}, - {"name": "phone", "generator": "phone_number"}, - {"name": "url", "generator": "url"}, - {"name": "domain", "generator": "domain_name"}, - {"name": "ipv4", "generator": "ipv4"}, - {"name": "ipv6", "generator": "ipv6"}, - {"name": "mac_address", "generator": "mac_address"}, - {"name": "date_field", "generator": "date"}, - {"name": "past_date", "generator": "past_date"}, - {"name": "future_date", "generator": "future_date"}, - {"name": "word", "generator": "word"}, - {"name": "sentence", "generator": "sentence"}, - {"name": "paragraph", "generator": "paragraph"}, - {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, - {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, - {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, - {"name": "is_active", "generator": "boolean"}, - {"name": "uuid", "generator": "uuid"}, - {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, - {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} - ] - } - ] - } - EOF + }') \ + > test-schema.json + + # Verify the schema has all expected fields + FIELD_COUNT=$(jq '.populate[0].fields | length' test-schema.json) + echo "Schema contains $FIELD_COUNT generator fields" + [ "$FIELD_COUNT" -eq 83 ] || (echo "❌ Expected 83 fields, got $FIELD_COUNT" && exit 1) - name: Test create and populate run: | @@ -799,105 +384,22 @@ jobs: - name: Create test schema run: | - cat > test-schema.json << 'EOF' - { - "database": { - "dbtype": "cockroachdb", + # Merge centralized schema with database-specific config + jq -s '.[0] * {"database": .[1]}' \ + .github/workflows/test-schema.json \ + <(echo '{ + "dbtype": "postgres", "username": "root", "password": "", "host": "localhost:26257", "database": "testdb" - }, - "tables": [ - { - "name": "comprehensive_test", - "columns": [ - {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, - {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "latitude", "type": "float"}, - {"name": "longitude", "type": "float"}, - {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, - {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, - {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, - {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, - {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, - {"name": "date_field", "type": "date"}, - {"name": "past_date", "type": "date"}, - {"name": "future_date", "type": "date"}, - {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, - {"name": "paragraph", "type": "text"}, - {"name": "random_int", "type": "integer"}, - {"name": "salary", "type": "float"}, - {"name": "price", "type": "float"}, - {"name": "is_active", "type": "boolean"}, - {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, - {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, - {"name": "priority", "type": {"name": "string", "args": {"length": 20}}} - ] - } - ], - "populate": [ - { - "name": "comprehensive_test", - "count": 10, - "fields": [ - {"name": "first_name", "generator": "first_name"}, - {"name": "last_name", "generator": "last_name"}, - {"name": "full_name", "generator": "name"}, - {"name": "username", "generator": "user_name"}, - {"name": "email", "generator": "email"}, - {"name": "password", "generator": "password"}, - {"name": "gender", "generator": "gender"}, - {"name": "address", "generator": "address"}, - {"name": "street_address", "generator": "street_address"}, - {"name": "city", "generator": "city"}, - {"name": "state", "generator": "state"}, - {"name": "country", "generator": "country"}, - {"name": "postcode", "generator": "postcode"}, - {"name": "latitude", "generator": "latitude"}, - {"name": "longitude", "generator": "longitude"}, - {"name": "company", "generator": "company"}, - {"name": "job_title", "generator": "job"}, - {"name": "phone", "generator": "phone_number"}, - {"name": "url", "generator": "url"}, - {"name": "domain", "generator": "domain_name"}, - {"name": "ipv4", "generator": "ipv4"}, - {"name": "ipv6", "generator": "ipv6"}, - {"name": "mac_address", "generator": "mac_address"}, - {"name": "date_field", "generator": "date"}, - {"name": "past_date", "generator": "past_date"}, - {"name": "future_date", "generator": "future_date"}, - {"name": "word", "generator": "word"}, - {"name": "sentence", "generator": "sentence"}, - {"name": "paragraph", "generator": "paragraph"}, - {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, - {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, - {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, - {"name": "is_active", "generator": "boolean"}, - {"name": "uuid", "generator": "uuid"}, - {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, - {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]} - ] - } - ] - } - EOF + }') \ + > test-schema.json + + # Verify the schema has all expected fields + FIELD_COUNT=$(jq '.populate[0].fields | length' test-schema.json) + echo "Schema contains $FIELD_COUNT generator fields" + [ "$FIELD_COUNT" -eq 83 ] || (echo "❌ Expected 83 fields, got $FIELD_COUNT" && exit 1) - name: Test create and populate run: | diff --git a/.github/workflows/test-schema.json b/.github/workflows/test-schema.json new file mode 100644 index 0000000..d8cd78f --- /dev/null +++ b/.github/workflows/test-schema.json @@ -0,0 +1,184 @@ +{ + "tables": [ + { + "name": "comprehensive_test", + "columns": [ + {"name": "id", "type": "integer", "options": {"primary_key": true, "autoincrement": true}}, + {"name": "first_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "last_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "full_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "username", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "email", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "password", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "gender", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "street_address", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "city", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "state", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "country", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "postcode", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "latitude", "type": "float"}, + {"name": "longitude", "type": "float"}, + {"name": "company", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "job_title", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "phone", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "url", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "domain", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "ipv4", "type": {"name": "string", "args": {"length": 15}}}, + {"name": "ipv6", "type": {"name": "string", "args": {"length": 45}}}, + {"name": "mac_address", "type": {"name": "string", "args": {"length": 17}}}, + {"name": "date_field", "type": "date"}, + {"name": "past_date", "type": "date"}, + {"name": "future_date", "type": "date"}, + {"name": "word", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "sentence", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "paragraph", "type": "text"}, + {"name": "random_int", "type": "integer"}, + {"name": "salary", "type": "float"}, + {"name": "price", "type": "float"}, + {"name": "is_active", "type": "boolean"}, + {"name": "uuid", "type": {"name": "string", "args": {"length": 36}}}, + {"name": "status", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "priority", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "credit_card", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "credit_card_type", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "currency", "type": {"name": "string", "args": {"length": 3}}}, + {"name": "currency_long", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "bitcoin_address", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "timestamp", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "time", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "year", "type": "integer"}, + {"name": "month", "type": "integer"}, + {"name": "weekday", "type": {"name": "string", "args": {"length": 20}}}, + {"name": "timezone", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "country_code", "type": {"name": "string", "args": {"length": 2}}}, + {"name": "language", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "locale", "type": {"name": "string", "args": {"length": 10}}}, + {"name": "color", "type": {"name": "string", "args": {"length": 30}}}, + {"name": "hex_color", "type": {"name": "string", "args": {"length": 7}}}, + {"name": "product_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "product_category", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "product_price", "type": "float"}, + {"name": "filename", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "file_extension", "type": {"name": "string", "args": {"length": 10}}}, + {"name": "mime_type", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "image_url", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "user_agent", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "book_title", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "book_author", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "book_genre", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "movie_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "animal", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "pet_name", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "cat", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "dog", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "fruit", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "vegetable", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "breakfast", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "dessert", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "drink", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "car_maker", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "car_model", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "car_type", "type": {"name": "string", "args": {"length": 50}}}, + {"name": "ssn", "type": {"name": "string", "args": {"length": 11}}}, + {"name": "iban", "type": {"name": "string", "args": {"length": 34}}}, + {"name": "emoji", "type": {"name": "string", "args": {"length": 10}}}, + {"name": "quote", "type": {"name": "string", "args": {"length": 255}}}, + {"name": "phrase", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "app_name", "type": {"name": "string", "args": {"length": 100}}}, + {"name": "app_version", "type": {"name": "string", "args": {"length": 20}}} + ] + } + ], + "populate": [ + { + "name": "comprehensive_test", + "count": 10, + "fields": [ + {"name": "first_name", "generator": "first_name"}, + {"name": "last_name", "generator": "last_name"}, + {"name": "full_name", "generator": "name"}, + {"name": "username", "generator": "user_name"}, + {"name": "email", "generator": "email"}, + {"name": "password", "generator": "password"}, + {"name": "gender", "generator": "gender"}, + {"name": "address", "generator": "address"}, + {"name": "street_address", "generator": "street_address"}, + {"name": "city", "generator": "city"}, + {"name": "state", "generator": "state"}, + {"name": "country", "generator": "country"}, + {"name": "postcode", "generator": "postcode"}, + {"name": "latitude", "generator": "latitude"}, + {"name": "longitude", "generator": "longitude"}, + {"name": "company", "generator": "company"}, + {"name": "job_title", "generator": "job"}, + {"name": "phone", "generator": "phone_number"}, + {"name": "url", "generator": "url"}, + {"name": "domain", "generator": "domain_name"}, + {"name": "ipv4", "generator": "ipv4"}, + {"name": "ipv6", "generator": "ipv6"}, + {"name": "mac_address", "generator": "mac_address"}, + {"name": "date_field", "generator": "date"}, + {"name": "past_date", "generator": "past_date"}, + {"name": "future_date", "generator": "future_date"}, + {"name": "word", "generator": "word"}, + {"name": "sentence", "generator": "sentence"}, + {"name": "paragraph", "generator": "paragraph"}, + {"name": "random_int", "generator": "random_int", "args": {"min": 1, "max": 1000}}, + {"name": "salary", "generator": "float", "args": {"min": 30000.0, "max": 150000.0}}, + {"name": "price", "generator": "decimal", "args": {"min": 9.99, "max": 999.99}}, + {"name": "is_active", "generator": "boolean"}, + {"name": "uuid", "generator": "uuid"}, + {"name": "status", "generator": "random_from", "args": ["active", "inactive", "pending", "suspended"]}, + {"name": "priority", "generator": "random_from", "args": ["low", "medium", "high", "urgent"]}, + {"name": "credit_card", "generator": "credit_card"}, + {"name": "credit_card_type", "generator": "credit_card_type"}, + {"name": "currency", "generator": "currency"}, + {"name": "currency_long", "generator": "currency_long"}, + {"name": "bitcoin_address", "generator": "bitcoin_address"}, + {"name": "timestamp", "generator": "timestamp"}, + {"name": "time", "generator": "time"}, + {"name": "year", "generator": "year"}, + {"name": "month", "generator": "month"}, + {"name": "weekday", "generator": "weekday"}, + {"name": "timezone", "generator": "timezone"}, + {"name": "country_code", "generator": "country_code"}, + {"name": "language", "generator": "language"}, + {"name": "locale", "generator": "locale"}, + {"name": "color", "generator": "color"}, + {"name": "hex_color", "generator": "hex_color"}, + {"name": "product_name", "generator": "product_name"}, + {"name": "product_category", "generator": "product_category"}, + {"name": "product_price", "generator": "price"}, + {"name": "filename", "generator": "filename"}, + {"name": "file_extension", "generator": "file_extension"}, + {"name": "mime_type", "generator": "mime_type"}, + {"name": "image_url", "generator": "image_url"}, + {"name": "user_agent", "generator": "user_agent"}, + {"name": "book_title", "generator": "book_title"}, + {"name": "book_author", "generator": "book_author"}, + {"name": "book_genre", "generator": "book_genre"}, + {"name": "movie_name", "generator": "movie_name"}, + {"name": "animal", "generator": "animal"}, + {"name": "pet_name", "generator": "pet_name"}, + {"name": "cat", "generator": "cat"}, + {"name": "dog", "generator": "dog"}, + {"name": "fruit", "generator": "fruit"}, + {"name": "vegetable", "generator": "vegetable"}, + {"name": "breakfast", "generator": "breakfast"}, + {"name": "dessert", "generator": "dessert"}, + {"name": "drink", "generator": "drink"}, + {"name": "car_maker", "generator": "car_maker"}, + {"name": "car_model", "generator": "car_model"}, + {"name": "car_type", "generator": "car_type"}, + {"name": "ssn", "generator": "ssn"}, + {"name": "iban", "generator": "iban"}, + {"name": "emoji", "generator": "emoji"}, + {"name": "quote", "generator": "quote"}, + {"name": "phrase", "generator": "phrase"}, + {"name": "app_name", "generator": "app_name"}, + {"name": "app_version", "generator": "app_version"} + ] + } + ] +} From b6de8053788f13daf0bece298357a00ca975ca10 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 11:36:44 +0530 Subject: [PATCH 17/19] feat: enhance centralized test schema with comprehensive validation and reduced workflow size --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eb0cc8..76ee496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Manual trigger via GitHub Actions UI (`workflow_dispatch`) - Individual checkboxes for selective database testing (all selected by default) - Docker-based testing for MySQL, PostgreSQL, MariaDB, MSSQL, SQLite, CockroachDB +- **Centralized Test Schema**: Created `.github/workflows/comprehensive-test-schema.json` + - Single source of truth for all database integration tests + - 84 columns covering all 116+ generators (83 fields + 1 auto-increment ID) + - Automatic field count validation to prevent missing generators + - Schema merging with database-specific configuration using `jq` + - Reduced test workflow from 1,477 to 432 lines (71% reduction) + - Ensures consistency across all 6 database tests + - See `.github/workflows/README.md` for documentation - **Documentation**: Added comprehensive configuration examples and Docker setup for new databases - **Version Selector**: Added version dropdown to documentation (powered by mike) - **Uninstall Script**: Created `scripts/uninstall.sh` for removing fakestack from npm, pip, and Homebrew From 8e8758eee6fa3164f066260e7571ccd70076e516 Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 15:03:46 +0530 Subject: [PATCH 18/19] fix: resolve database test failures - Increase locale column length from 10 to 20 characters (fixes MySQL/MariaDB data truncation) - Combine -c and -p flags into single command (fixes table creation issues) - Fixes errors in PostgreSQL, CockroachDB, and SQLite tests Issues fixed: - MySQL/MariaDB: Data too long for column 'locale' at row 1 - PostgreSQL/CockroachDB: Relation does not exist errors - SQLite: No such table errors after creation --- .github/workflows/test-databases.yml | 18 ++++++------------ .github/workflows/test-schema.json | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test-databases.yml b/.github/workflows/test-databases.yml index 7235d5d..2a422bd 100644 --- a/.github/workflows/test-databases.yml +++ b/.github/workflows/test-databases.yml @@ -91,8 +91,7 @@ jobs: - name: Test create and populate run: | - ./bin/fakestack-test -c -f test-schema.json - ./bin/fakestack-test -p -f test-schema.json + ./bin/fakestack-test -c -p -f test-schema.json - name: Verify data run: | @@ -156,8 +155,7 @@ jobs: - name: Test create and populate run: | - ./bin/fakestack-test -c -f test-schema.json - ./bin/fakestack-test -p -f test-schema.json + ./bin/fakestack-test -c -p -f test-schema.json - name: Verify data run: | @@ -221,8 +219,7 @@ jobs: - name: Test create and populate run: | - ./bin/fakestack-test -c -f test-schema.json - ./bin/fakestack-test -p -f test-schema.json + ./bin/fakestack-test -c -p -f test-schema.json - name: Verify data run: | @@ -269,8 +266,7 @@ jobs: - name: Test create and populate run: | - ./bin/fakestack-test -c -f test-schema.json - ./bin/fakestack-test -p -f test-schema.json + ./bin/fakestack-test -c -p -f test-schema.json - name: Verify data run: | @@ -339,8 +335,7 @@ jobs: - name: Test create and populate run: | - ./bin/fakestack-test -c -f test-schema.json - ./bin/fakestack-test -p -f test-schema.json + ./bin/fakestack-test -c -p -f test-schema.json - name: Verify data run: | @@ -403,8 +398,7 @@ jobs: - name: Test create and populate run: | - ./bin/fakestack-test -c -f test-schema.json - ./bin/fakestack-test -p -f test-schema.json + ./bin/fakestack-test -c -p -f test-schema.json - name: Verify data run: | diff --git a/.github/workflows/test-schema.json b/.github/workflows/test-schema.json index d8cd78f..b196db9 100644 --- a/.github/workflows/test-schema.json +++ b/.github/workflows/test-schema.json @@ -53,7 +53,7 @@ {"name": "timezone", "type": {"name": "string", "args": {"length": 50}}}, {"name": "country_code", "type": {"name": "string", "args": {"length": 2}}}, {"name": "language", "type": {"name": "string", "args": {"length": 50}}}, - {"name": "locale", "type": {"name": "string", "args": {"length": 10}}}, + {"name": "locale", "type": {"name": "string", "args": {"length": 20}}}, {"name": "color", "type": {"name": "string", "args": {"length": 30}}}, {"name": "hex_color", "type": {"name": "string", "args": {"length": 7}}}, {"name": "product_name", "type": {"name": "string", "args": {"length": 100}}}, From 27190453988e90e6006a3fe5cd6ffc070b36863f Mon Sep 17 00:00:00 2001 From: Devendra Pratap Singh Date: Tue, 18 Nov 2025 15:08:07 +0530 Subject: [PATCH 19/19] fix: correct MSSQL password in test schema to match service configuration --- .github/workflows/test-databases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-databases.yml b/.github/workflows/test-databases.yml index 2a422bd..6ddaf74 100644 --- a/.github/workflows/test-databases.yml +++ b/.github/workflows/test-databases.yml @@ -322,7 +322,7 @@ jobs: <(echo '{ "dbtype": "mssql", "username": "sa", - "password": "YourStrong@Passw0rd", + "password": "TestPass123!", "host": "localhost:1433", "database": "testdb" }') \