first commit
This commit is contained in:
122
tests/scripts/1millogs/Makefile
Normal file
122
tests/scripts/1millogs/Makefile
Normal file
@@ -0,0 +1,122 @@
|
||||
.PHONY: help populate-sqlite populate-postgres populate-small populate-large clean
|
||||
|
||||
help:
|
||||
@echo "Bifrost Test Scripts"
|
||||
@echo "===================="
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@echo " populate-sqlite - Populate SQLite with 1M rows (~17.5 GB)"
|
||||
@echo " populate-postgres - Populate PostgreSQL with 1M rows (~17.5 GB)"
|
||||
@echo " populate-small - Quick test with 10K rows (~175 MB)"
|
||||
@echo " populate-large - Large dataset with 2M rows (~35 GB)"
|
||||
@echo " clean - Remove generated SQLite database"
|
||||
@echo ""
|
||||
@echo "Options:"
|
||||
@echo " TYPE=llm|mcp - Log type to generate (default: llm)"
|
||||
@echo " INVALID_META=1 - Generate all logs with invalid JSON metadata"
|
||||
@echo ""
|
||||
@echo "Examples:"
|
||||
@echo " make populate-sqlite"
|
||||
@echo " make populate-postgres POSTGRES_PASSWORD=yourpassword"
|
||||
@echo " make populate-postgres TYPE=mcp ROWS=100000"
|
||||
@echo " make populate-postgres INVALID_META=1"
|
||||
@echo ""
|
||||
|
||||
# Default values
|
||||
TYPE ?= llm
|
||||
ROWS ?= 1000000
|
||||
SIZE ?= 17.5
|
||||
BATCH ?= 1000
|
||||
DB_PATH ?= ../../tests/configs/default/logs.db
|
||||
INVALID_META ?=
|
||||
|
||||
# Derive flag from INVALID_META
|
||||
INVALID_META_FLAG = $(if $(INVALID_META),-invalid-metadata,)
|
||||
|
||||
POSTGRES_HOST ?= localhost
|
||||
POSTGRES_PORT ?= 5432
|
||||
POSTGRES_USER ?= bifrost
|
||||
POSTGRES_PASSWORD ?= bifrost_password
|
||||
POSTGRES_DB ?= bifrost
|
||||
|
||||
# Populate SQLite database (default)
|
||||
populate-sqlite:
|
||||
@echo "🚀 Populating SQLite database..."
|
||||
@echo " Type: $(TYPE)"
|
||||
@echo " Rows: $(ROWS)"
|
||||
@echo " Target size: $(SIZE) GB"
|
||||
@echo " Database: $(DB_PATH)"
|
||||
@echo ""
|
||||
go run main.go \
|
||||
-type $(TYPE) \
|
||||
-db sqlite \
|
||||
-path $(DB_PATH) \
|
||||
-rows $(ROWS) \
|
||||
-size $(SIZE) \
|
||||
-batch 50 \
|
||||
$(INVALID_META_FLAG)
|
||||
|
||||
# Populate PostgreSQL database
|
||||
populate-postgres:
|
||||
@echo "🚀 Populating PostgreSQL database..."
|
||||
@echo " Type: $(TYPE)"
|
||||
@echo " Rows: $(ROWS)"
|
||||
@echo " Target size: $(SIZE) GB"
|
||||
@echo " Host: $(POSTGRES_HOST):$(POSTGRES_PORT)"
|
||||
@echo " Database: $(POSTGRES_DB)"
|
||||
@echo ""
|
||||
go run main.go \
|
||||
-type $(TYPE) \
|
||||
-db postgres \
|
||||
-host $(POSTGRES_HOST) \
|
||||
-port $(POSTGRES_PORT) \
|
||||
-user $(POSTGRES_USER) \
|
||||
-password $(POSTGRES_PASSWORD) \
|
||||
-dbname $(POSTGRES_DB) \
|
||||
-rows $(ROWS) \
|
||||
-size $(SIZE) \
|
||||
-batch $(BATCH) \
|
||||
$(INVALID_META_FLAG)
|
||||
|
||||
# Quick test with small dataset
|
||||
populate-small:
|
||||
@echo "🚀 Creating small test dataset..."
|
||||
@$(MAKE) populate-sqlite ROWS=10000 SIZE=0.175
|
||||
|
||||
# Large dataset for stress testing
|
||||
populate-large:
|
||||
@echo "🚀 Creating large test dataset..."
|
||||
@$(MAKE) populate-sqlite ROWS=2000000 SIZE=35
|
||||
|
||||
# Custom parameters
|
||||
populate-custom:
|
||||
@if [ -z "$(ROWS)" ] || [ -z "$(SIZE)" ]; then \
|
||||
echo "❌ Error: ROWS and SIZE parameters are required"; \
|
||||
echo "Usage: make populate-custom ROWS=500000 SIZE=8.75"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "🚀 Creating custom dataset..."
|
||||
@$(MAKE) populate-sqlite ROWS=$(ROWS) SIZE=$(SIZE)
|
||||
|
||||
# Clean up generated files
|
||||
clean:
|
||||
@echo "🧹 Cleaning up..."
|
||||
rm -f $(DB_PATH)
|
||||
rm -f $(DB_PATH)-shm
|
||||
rm -f $(DB_PATH)-wal
|
||||
@echo "✅ Done!"
|
||||
|
||||
# Derive table name from TYPE
|
||||
TABLE_NAME = $(if $(filter mcp,$(TYPE)),mcp_tool_logs,logs)
|
||||
|
||||
# Get database stats
|
||||
stats:
|
||||
@if [ -f "$(DB_PATH)" ]; then \
|
||||
echo "📊 Database Statistics"; \
|
||||
echo "======================"; \
|
||||
du -h $(DB_PATH) | awk '{print "Size: " $$1}'; \
|
||||
sqlite3 $(DB_PATH) "SELECT COUNT(*) FROM $(TABLE_NAME)" | awk '{print "Rows: " $$1}'; \
|
||||
else \
|
||||
echo "❌ Database not found at: $(DB_PATH)"; \
|
||||
fi
|
||||
|
||||
1
tests/scripts/1millogs/README.md
Normal file
1
tests/scripts/1millogs/README.md
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
91
tests/scripts/1millogs/go.mod
Normal file
91
tests/scripts/1millogs/go.mod
Normal file
@@ -0,0 +1,91 @@
|
||||
module github.com/maximhq/bifrost/tests/scripts/1millogs
|
||||
|
||||
go 1.26.2
|
||||
|
||||
require (
|
||||
github.com/maximhq/bifrost/core v1.5.4
|
||||
github.com/maximhq/bifrost/framework v0.0.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.2 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.9.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/mark3labs/mcp-go v0.43.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.68.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.starlark.net v0.0.0-20260102030733-3fee463870c9 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/maximhq/bifrost/core => ../../../core
|
||||
|
||||
replace github.com/maximhq/bifrost/framework => ../../../framework
|
||||
222
tests/scripts/1millogs/go.sum
Normal file
222
tests/scripts/1millogs/go.sum
Normal file
@@ -0,0 +1,222 @@
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11/go.mod h1:twF11+6ps9aNRKEDimksp923o44w/Thk9+8YIlzWMmo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11/go.mod h1:30yY2zqkMPdrvxBqzI9xQCM+WrlrZKSOpSJEsylVU+8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 h1:INUvJxmhdEbVulJYHI061k4TVuS3jzzthNvjqvVvTKM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19/go.mod h1:FpZN2QISLdEBWkayloda+sZjVJL+e9Gl0k1SyTgcswU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 h1:/sECfyq2JTifMI2JPyZ4bdRN77zJmr6SrS1eL3augIA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19/go.mod h1:dMf8A5oAqr9/oxOfLkC/c2LU/uMcALP0Rgn2BD5LWn0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJe6qZoRSJo0WfUhsMdUKhoODXE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7suZk9UCJHE1Iw9GMZJJl0dAnKXXP1NaSDHwmw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 h1:SWTxh/EcUCDVqi/0s26V6pVUq0BBG7kx0tDTmF/hCgA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12/go.mod h1:fEWYKTRGoZNl8tZ77i61/ccwOMJdGxwOhWCkp6TXAr0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 h1:EnUdUqRP1CNzt2DkV67tJx6XDN4xlfBFm+bzeNOQVb0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16/go.mod h1:Jic/xv0Rq/pFNCh3WwpH4BEqdbSAl+IyHro8LbibHD8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 h1:XQTQTF75vnug2TXS8m7CVJfC2nniYPZnO1D4Np761Oo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8/go.mod h1:Xgx+PR1NUOjNmQY+tRMnouRp83JRM8pRMw/vCaVhPkI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
|
||||
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE=
|
||||
github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
|
||||
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4=
|
||||
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
|
||||
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
go.starlark.net v0.0.0-20260102030733-3fee463870c9 h1:nV1OyvU+0CYrp5eKfQ3rD03TpFYYhH08z31NK1HmtTk=
|
||||
go.starlark.net v0.0.0-20260102030733-3fee463870c9/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
789
tests/scripts/1millogs/main.go
Normal file
789
tests/scripts/1millogs/main.go
Normal file
@@ -0,0 +1,789 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
bifrost "github.com/maximhq/bifrost/core"
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
"github.com/maximhq/bifrost/framework/logstore"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
// Command line flags
|
||||
dbType = flag.String("db", "sqlite", "Database type: sqlite or postgres")
|
||||
dbPath = flag.String("path", "./tests/configs/default/logs.db", "Path to SQLite database")
|
||||
dbHost = flag.String("host", "localhost", "PostgreSQL host")
|
||||
dbPort = flag.Int("port", 5432, "PostgreSQL port")
|
||||
dbUser = flag.String("user", "postgres", "PostgreSQL user")
|
||||
dbPassword = flag.String("password", "", "PostgreSQL password")
|
||||
dbName = flag.String("dbname", "bifrost_logs", "PostgreSQL database name")
|
||||
numRows = flag.Int("rows", 1000000, "Number of rows to insert")
|
||||
batchSize = flag.Int("batch", 1000, "Batch size for inserts")
|
||||
targetSizeGB = flag.Float64("size", 17.5, "Target size in GB (will adjust row size)")
|
||||
logType = flag.String("type", "llm", "Log type to generate: llm or mcp")
|
||||
invalidMeta = flag.Bool("invalid-metadata", false, "When set, all logs will have invalid JSON metadata")
|
||||
)
|
||||
|
||||
// Providers and models for variety
|
||||
var providers = []string{"openai", "anthropic", "cohere", "azure", "gemini", "mistral"}
|
||||
var models = map[string][]string{
|
||||
"openai": {"gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"},
|
||||
"anthropic": {"claude-3-5-sonnet-20241022", "claude-3-opus-20240229", "claude-3-haiku-20240307"},
|
||||
"cohere": {"command-r-plus", "command-r", "command"},
|
||||
"azure": {"gpt-4o", "gpt-35-turbo"},
|
||||
"gemini": {"gemini-1.5-pro", "gemini-1.5-flash", "gemini-pro"},
|
||||
"mistral": {"mistral-large-2411", "mistral-medium", "mistral-small"},
|
||||
}
|
||||
|
||||
var statuses = []string{"success", "error", "processing"}
|
||||
var objects = []string{"chat.completion", "text.completion", "embedding"}
|
||||
|
||||
// validMetadataValues are realistic metadata samples used by ~15% of generated logs.
|
||||
var validMetadataValues = []string{
|
||||
`{"environment": "production", "version": "1.0.0"}`,
|
||||
`{"team": "backend", "project": "api-gateway"}`,
|
||||
`{"user_id": "user-123", "session": "sess-abc"}`,
|
||||
`{"source": "web", "region": "us-east-1", "tier": "premium"}`,
|
||||
`{"app": "mobile", "os": "ios", "release": "2.4.1"}`,
|
||||
}
|
||||
|
||||
// invalidMetadataValues are malformed JSON strings used when --invalid-metadata is set.
|
||||
var invalidMetadataValues = []string{
|
||||
`{"environment": "production", "version":}`,
|
||||
`{"team": "backend", "project"`,
|
||||
`{not_quoted_key: "value"}`,
|
||||
`{"key": "value",}`,
|
||||
`{"nested": {"a": 1, "b": [2, 3}}}`,
|
||||
`just plain text, not json at all`,
|
||||
`<xml>not json</xml>`,
|
||||
`{"unterminated": "string value`,
|
||||
}
|
||||
|
||||
// MCP tool names and server labels for MCP log generation
|
||||
var mcpToolNames = []string{"get_weather", "search_docs", "execute_query", "send_email", "create_ticket", "fetch_url", "list_files", "run_code"}
|
||||
var mcpServerLabels = []string{"weather-service", "doc-search", "db-query", "email-service", "ticketing", "web-fetcher", "file-manager", "code-runner"}
|
||||
|
||||
var mcpToolArguments = []map[string]interface{}{
|
||||
{"city": "San Francisco", "units": "celsius"},
|
||||
{"query": "kubernetes deployment best practices", "limit": 10},
|
||||
{"sql": "SELECT * FROM users WHERE active = true LIMIT 100", "database": "analytics"},
|
||||
{"to": "team@example.com", "subject": "Deploy complete", "body": "v2.4.1 deployed successfully"},
|
||||
{"title": "Fix login timeout", "priority": "high", "assignee": "backend-team"},
|
||||
{"url": "https://api.example.com/health", "method": "GET", "timeout": 5000},
|
||||
{"path": "/var/log/app", "pattern": "*.log", "recursive": true},
|
||||
{"language": "python", "code": "print('hello world')", "timeout": 30},
|
||||
}
|
||||
|
||||
var mcpToolResults = []map[string]interface{}{
|
||||
{"temperature": 18.5, "condition": "partly cloudy", "humidity": 65},
|
||||
{"results": []string{"doc-1", "doc-2", "doc-3"}, "total": 42},
|
||||
{"rows_affected": 156, "execution_time_ms": 23},
|
||||
{"message_id": "msg-abc123", "status": "sent"},
|
||||
{"ticket_id": "TICK-4521", "url": "https://tickets.example.com/TICK-4521"},
|
||||
{"status_code": 200, "body": "{\"status\":\"healthy\"}", "latency_ms": 45},
|
||||
{"files": []string{"app.log", "error.log", "access.log"}, "count": 3},
|
||||
{"stdout": "hello world\n", "exit_code": 0, "duration_ms": 150},
|
||||
}
|
||||
|
||||
var mcpErrorMessages = []string{
|
||||
"Connection timeout after 30s",
|
||||
"Permission denied: insufficient scope",
|
||||
"Rate limit exceeded: 100 requests/min",
|
||||
"Resource not found: document-id-xyz",
|
||||
"Internal server error in downstream service",
|
||||
"Invalid arguments: missing required field 'query'",
|
||||
}
|
||||
|
||||
// Sample conversation content for realistic data
|
||||
var userPrompts = []string{
|
||||
"Explain quantum computing in simple terms.",
|
||||
"Write a Python function to calculate fibonacci numbers.",
|
||||
"What are the best practices for React application architecture?",
|
||||
"How does blockchain technology work?",
|
||||
"Explain the difference between SQL and NoSQL databases.",
|
||||
"What are microservices and when should I use them?",
|
||||
"How do I optimize database queries for better performance?",
|
||||
"Explain machine learning model training process.",
|
||||
"What is the CAP theorem in distributed systems?",
|
||||
"How does OAuth 2.0 authentication work?",
|
||||
"Explain the SOLID principles in software engineering.",
|
||||
"What are the key differences between REST and GraphQL?",
|
||||
"How do I implement caching strategies in web applications?",
|
||||
"Explain Docker containers and their benefits.",
|
||||
"What is the difference between synchronous and asynchronous programming?",
|
||||
}
|
||||
|
||||
var assistantResponses = []string{
|
||||
`Quantum computing is a revolutionary approach to computation that leverages quantum mechanical phenomena. Unlike classical computers that use bits (0 or 1), quantum computers use quantum bits or "qubits" that can exist in multiple states simultaneously through superposition. This allows them to process vast amounts of information in parallel. Key concepts include:
|
||||
|
||||
1. **Superposition**: Qubits can be in multiple states at once, unlike classical bits which are either 0 or 1.
|
||||
2. **Entanglement**: Qubits can be correlated with each other in ways that classical bits cannot.
|
||||
3. **Interference**: Quantum algorithms use interference to amplify correct answers and cancel out wrong ones.
|
||||
|
||||
Quantum computers excel at specific tasks like:
|
||||
- Cryptography and code breaking
|
||||
- Optimization problems
|
||||
- Molecular simulation
|
||||
- Machine learning
|
||||
|
||||
However, they face challenges like decoherence (losing quantum state) and error correction. Current quantum computers are in the NISQ (Noisy Intermediate-Scale Quantum) era, meaning they're still developing and prone to errors.`,
|
||||
|
||||
`Here's a comprehensive Python implementation for calculating Fibonacci numbers with multiple approaches:
|
||||
|
||||
` + "```python" + `
|
||||
def fibonacci_iterative(n):
|
||||
"""Calculate nth Fibonacci number iteratively - O(n) time, O(1) space"""
|
||||
if n <= 1:
|
||||
return n
|
||||
|
||||
prev, curr = 0, 1
|
||||
for _ in range(2, n + 1):
|
||||
prev, curr = curr, prev + curr
|
||||
return curr
|
||||
|
||||
def fibonacci_recursive(n, memo=None):
|
||||
"""Calculate nth Fibonacci with memoization - O(n) time and space"""
|
||||
if memo is None:
|
||||
memo = {}
|
||||
|
||||
if n in memo:
|
||||
return memo[n]
|
||||
|
||||
if n <= 1:
|
||||
return n
|
||||
|
||||
memo[n] = fibonacci_recursive(n-1, memo) + fibonacci_recursive(n-2, memo)
|
||||
return memo[n]
|
||||
|
||||
def fibonacci_generator(limit):
|
||||
"""Generate Fibonacci sequence up to limit"""
|
||||
a, b = 0, 1
|
||||
while a < limit:
|
||||
yield a
|
||||
a, b = b, a + b
|
||||
|
||||
# Usage examples
|
||||
print(fibonacci_iterative(10)) # Output: 55
|
||||
print(fibonacci_recursive(10)) # Output: 55
|
||||
|
||||
# Generate sequence
|
||||
for num in fibonacci_generator(100):
|
||||
print(num, end=' ')
|
||||
` + "```" + `
|
||||
|
||||
The iterative approach is most efficient for single calculations, while the generator is best for sequences.`,
|
||||
|
||||
`React application architecture best practices involve several key principles and patterns:
|
||||
|
||||
**1. Component Organization**
|
||||
- Feature-based folder structure over type-based
|
||||
- Separate presentational from container components
|
||||
- Keep components small and focused (Single Responsibility)
|
||||
|
||||
**2. State Management**
|
||||
- Use Context API for simple global state
|
||||
- Redux/Zustand for complex state management
|
||||
- React Query for server state
|
||||
- Keep state as local as possible
|
||||
|
||||
**3. Code Structure**
|
||||
` + "```" + `
|
||||
src/
|
||||
├── features/ # Feature-based modules
|
||||
│ ├── auth/
|
||||
│ ├── dashboard/
|
||||
│ └── profile/
|
||||
├── components/ # Shared components
|
||||
├── hooks/ # Custom hooks
|
||||
├── utils/ # Helper functions
|
||||
├── services/ # API calls
|
||||
└── contexts/ # Context providers
|
||||
` + "```" + `
|
||||
|
||||
**4. Performance Optimization**
|
||||
- Use React.memo() for expensive components
|
||||
- Implement code splitting with lazy loading
|
||||
- Optimize re-renders with useMemo and useCallback
|
||||
- Use virtualization for long lists
|
||||
|
||||
**5. Testing Strategy**
|
||||
- Unit tests for utilities and hooks
|
||||
- Integration tests for features
|
||||
- E2E tests for critical user flows
|
||||
|
||||
**6. Error Handling**
|
||||
- Error boundaries for graceful failures
|
||||
- Proper loading and error states
|
||||
- User-friendly error messages`,
|
||||
|
||||
`Blockchain technology is a distributed ledger system that maintains a continuously growing list of records called blocks. Here's how it works:
|
||||
|
||||
**Core Concepts:**
|
||||
|
||||
1. **Blocks**: Each block contains:
|
||||
- Transaction data
|
||||
- Timestamp
|
||||
- Hash of previous block
|
||||
- Cryptographic hash of current block
|
||||
|
||||
2. **Chain Structure**: Blocks are linked chronologically, forming an immutable chain. Changing any block would invalidate all subsequent blocks.
|
||||
|
||||
3. **Decentralization**: Instead of a central authority, the blockchain is distributed across a network of nodes (computers).
|
||||
|
||||
**How It Works:**
|
||||
|
||||
1. **Transaction Initiation**: User requests a transaction
|
||||
2. **Broadcasting**: Transaction is broadcast to all nodes in the network
|
||||
3. **Validation**: Nodes validate the transaction using consensus mechanisms
|
||||
4. **Block Creation**: Valid transactions are combined into a new block
|
||||
5. **Mining/Consensus**: Nodes compete to validate the block (Proof of Work) or use other consensus mechanisms (Proof of Stake)
|
||||
6. **Addition to Chain**: Once validated, the block is added to the chain
|
||||
7. **Distribution**: Updated blockchain is distributed across the network
|
||||
|
||||
**Key Features:**
|
||||
- **Immutability**: Once recorded, data cannot be altered retroactively
|
||||
- **Transparency**: All transactions are visible to network participants
|
||||
- **Security**: Cryptographic hashing makes tampering extremely difficult
|
||||
- **Decentralization**: No single point of failure or control
|
||||
|
||||
**Use Cases:**
|
||||
- Cryptocurrencies (Bitcoin, Ethereum)
|
||||
- Supply chain tracking
|
||||
- Smart contracts
|
||||
- Digital identity
|
||||
- Healthcare records`,
|
||||
|
||||
`SQL and NoSQL databases serve different purposes and have distinct characteristics:
|
||||
|
||||
**SQL Databases (Relational)**
|
||||
|
||||
*Structure:*
|
||||
- Fixed schema with tables, rows, and columns
|
||||
- Relationships defined through foreign keys
|
||||
- ACID compliant (Atomicity, Consistency, Isolation, Durability)
|
||||
|
||||
*Examples:* PostgreSQL, MySQL, Oracle, SQL Server
|
||||
|
||||
*Best For:*
|
||||
- Complex queries and joins
|
||||
- Transactions requiring consistency
|
||||
- Structured data with clear relationships
|
||||
- Applications requiring referential integrity
|
||||
|
||||
*Pros:*
|
||||
- Mature technology with extensive tools
|
||||
- Strong consistency guarantees
|
||||
- Powerful query language (SQL)
|
||||
- Good for complex relationships
|
||||
|
||||
*Cons:*
|
||||
- Less flexible schema
|
||||
- Vertical scaling challenges
|
||||
- Can be slower for simple key-value operations
|
||||
|
||||
**NoSQL Databases (Non-Relational)**
|
||||
|
||||
*Types:*
|
||||
1. Document stores (MongoDB, Couchbase)
|
||||
2. Key-value stores (Redis, DynamoDB)
|
||||
3. Column-family stores (Cassandra, HBase)
|
||||
4. Graph databases (Neo4j, Amazon Neptune)
|
||||
|
||||
*Best For:*
|
||||
- Flexible, evolving schemas
|
||||
- Horizontal scaling needs
|
||||
- High-velocity data ingestion
|
||||
- Unstructured or semi-structured data
|
||||
|
||||
*Pros:*
|
||||
- Flexible schema design
|
||||
- Excellent horizontal scalability
|
||||
- High performance for specific use cases
|
||||
- Better for hierarchical data
|
||||
|
||||
*Cons:*
|
||||
- Limited query capabilities
|
||||
- Eventual consistency (in many cases)
|
||||
- Lack of standardization
|
||||
- Fewer tools and expertise available
|
||||
|
||||
**Choosing Between Them:**
|
||||
- Use SQL when you need complex queries, transactions, and relationships
|
||||
- Use NoSQL when you need flexibility, scalability, and high performance for specific patterns
|
||||
- Many modern applications use both (polyglot persistence)`,
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *logType != "llm" && *logType != "mcp" {
|
||||
fmt.Printf("❌ Invalid log type: %s (must be 'llm' or 'mcp')\n", *logType)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("🚀 Bifrost Logs Population Script")
|
||||
fmt.Println("==================================")
|
||||
fmt.Printf("Log Type: %s\n", *logType)
|
||||
fmt.Printf("Invalid Metadata: %v\n", *invalidMeta)
|
||||
fmt.Printf("Database Type: %s\n", *dbType)
|
||||
fmt.Printf("Target Rows: %d\n", *numRows)
|
||||
fmt.Printf("Target Size: %.2f GB\n", *targetSizeGB)
|
||||
fmt.Printf("Batch Size: %d\n", *batchSize)
|
||||
fmt.Println()
|
||||
|
||||
// Calculate target size per row
|
||||
targetBytesPerRow := int((*targetSizeGB * 1024 * 1024 * 1024) / float64(*numRows))
|
||||
fmt.Printf("📊 Target size per row: ~%d bytes (%.2f KB)\n\n", targetBytesPerRow, float64(targetBytesPerRow)/1024)
|
||||
|
||||
// Connect to database
|
||||
db, err := connectDB()
|
||||
if err != nil {
|
||||
fmt.Printf("❌ Failed to connect to database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
fmt.Println("🔄 Running migrations...")
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
fmt.Printf("❌ Failed to get DB instance: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create table based on log type
|
||||
if *logType == "mcp" {
|
||||
if err := db.AutoMigrate(&logstore.MCPToolLog{}); err != nil {
|
||||
fmt.Printf("❌ Failed to migrate: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
if err := db.AutoMigrate(&logstore.Log{}); err != nil {
|
||||
fmt.Printf("❌ Failed to migrate: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("✅ Database ready")
|
||||
fmt.Println()
|
||||
|
||||
// Get current count
|
||||
var currentCount int64
|
||||
if *logType == "mcp" {
|
||||
db.Model(&logstore.MCPToolLog{}).Count(¤tCount)
|
||||
} else {
|
||||
db.Model(&logstore.Log{}).Count(¤tCount)
|
||||
}
|
||||
fmt.Printf("📈 Current rows in database: %d\n", currentCount)
|
||||
fmt.Printf("🎯 Will insert %d new rows\n\n", *numRows)
|
||||
|
||||
// Generate and insert logs
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
startTime := time.Now()
|
||||
totalInserted := 0
|
||||
|
||||
fmt.Println("⏳ Starting insertion...")
|
||||
fmt.Println()
|
||||
|
||||
for i := 0; i < *numRows; i += *batchSize {
|
||||
batchEnd := i + *batchSize
|
||||
if batchEnd > *numRows {
|
||||
batchEnd = *numRows
|
||||
}
|
||||
|
||||
if *logType == "mcp" {
|
||||
batch := make([]logstore.MCPToolLog, batchEnd-i)
|
||||
for j := range batch {
|
||||
batch[j] = generateMCPLog(i + j)
|
||||
}
|
||||
if err := db.Create(&batch).Error; err != nil {
|
||||
fmt.Printf("❌ Error inserting batch at row %d: %v\n", i, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
batch := make([]logstore.Log, batchEnd-i)
|
||||
for j := range batch {
|
||||
batch[j] = generateLog(i+j, targetBytesPerRow)
|
||||
}
|
||||
if err := db.Create(&batch).Error; err != nil {
|
||||
fmt.Printf("❌ Error inserting batch at row %d: %v\n", i, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
totalInserted += batchEnd - i
|
||||
|
||||
// Progress update
|
||||
if (i+*batchSize)%(*batchSize*10) == 0 || batchEnd == *numRows {
|
||||
elapsed := time.Since(startTime)
|
||||
progress := float64(totalInserted) / float64(*numRows) * 100
|
||||
rate := float64(totalInserted) / elapsed.Seconds()
|
||||
remaining := time.Duration(float64(*numRows-totalInserted)/rate) * time.Second
|
||||
|
||||
fmt.Printf("\r📊 Progress: %d/%d (%.1f%%) | Rate: %.0f rows/s | Elapsed: %s | ETA: %s",
|
||||
totalInserted, *numRows, progress, rate,
|
||||
elapsed.Round(time.Second), remaining.Round(time.Second))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
|
||||
// Final statistics
|
||||
elapsed := time.Since(startTime)
|
||||
|
||||
var finalCount int64
|
||||
if *logType == "mcp" {
|
||||
db.Model(&logstore.MCPToolLog{}).Count(&finalCount)
|
||||
} else {
|
||||
db.Model(&logstore.Log{}).Count(&finalCount)
|
||||
}
|
||||
|
||||
// Get database size
|
||||
var dbSize int64
|
||||
if *dbType == "sqlite" {
|
||||
fileInfo, err := os.Stat(*dbPath)
|
||||
if err == nil {
|
||||
dbSize = fileInfo.Size()
|
||||
}
|
||||
} else {
|
||||
// For PostgreSQL, query the database size
|
||||
db.Raw("SELECT pg_database_size(current_database())").Scan(&dbSize)
|
||||
}
|
||||
|
||||
dbSizeGB := float64(dbSize) / (1024 * 1024 * 1024)
|
||||
|
||||
fmt.Println("✅ Insertion Complete!")
|
||||
fmt.Println("======================")
|
||||
fmt.Printf("📊 Total rows inserted: %d\n", totalInserted)
|
||||
fmt.Printf("📊 Total rows in database: %d\n", finalCount)
|
||||
fmt.Printf("⏱️ Total time: %s\n", elapsed.Round(time.Second))
|
||||
fmt.Printf("⚡ Average rate: %.0f rows/second\n", float64(totalInserted)/elapsed.Seconds())
|
||||
fmt.Printf("💾 Database size: %.2f GB\n", dbSizeGB)
|
||||
fmt.Printf("📏 Average row size: %.2f KB\n", float64(dbSize)/float64(finalCount)/1024)
|
||||
|
||||
if dbSizeGB < *targetSizeGB*0.9 || dbSizeGB > *targetSizeGB*1.1 {
|
||||
fmt.Printf("\n⚠️ Database size (%.2f GB) is outside target range (%.2f GB ±10%%)\n", dbSizeGB, *targetSizeGB)
|
||||
fmt.Println("💡 Adjust the --size parameter and run again to fine-tune")
|
||||
}
|
||||
|
||||
sqlDB.Close()
|
||||
fmt.Println("\n🎉 Done!")
|
||||
}
|
||||
|
||||
func connectDB() (*gorm.DB, error) {
|
||||
config := &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
}
|
||||
|
||||
switch *dbType {
|
||||
case "sqlite":
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(*dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(*dbPath), config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open SQLite database: %w", err)
|
||||
}
|
||||
|
||||
// Optimize SQLite for bulk inserts
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sqlDB.Exec("PRAGMA journal_mode=WAL")
|
||||
sqlDB.Exec("PRAGMA synchronous=NORMAL")
|
||||
sqlDB.Exec("PRAGMA cache_size=1000000")
|
||||
sqlDB.Exec("PRAGMA temp_store=MEMORY")
|
||||
|
||||
return db, nil
|
||||
|
||||
case "postgres":
|
||||
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
||||
*dbHost, *dbPort, *dbUser, *dbPassword, *dbName)
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open PostgreSQL database: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported database type: %s", *dbType)
|
||||
}
|
||||
}
|
||||
|
||||
func generateLog(index int, targetSize int) logstore.Log {
|
||||
// Select random provider and model
|
||||
provider := providers[rand.Intn(len(providers))]
|
||||
modelList := models[provider]
|
||||
model := modelList[rand.Intn(len(modelList))]
|
||||
status := statuses[rand.Intn(len(statuses))]
|
||||
object := objects[rand.Intn(len(objects))]
|
||||
|
||||
// Generate timestamps
|
||||
timestamp := time.Now().Add(-time.Duration(rand.Intn(90*24)) * time.Hour) // Random time in last 90 days
|
||||
|
||||
// Generate chat history - scale based on target size
|
||||
numMessages := 2 + rand.Intn(5) // 2-6 messages
|
||||
inputHistory := make([]schemas.ChatMessage, numMessages)
|
||||
|
||||
// Create substantial messages to reach target size
|
||||
baseMessageSize := targetSize / (numMessages + 1) // +1 for output message
|
||||
|
||||
for i := range inputHistory {
|
||||
var content string
|
||||
var role schemas.ChatMessageRole
|
||||
|
||||
if i%2 == 0 {
|
||||
role = schemas.ChatMessageRoleUser
|
||||
content = userPrompts[rand.Intn(len(userPrompts))]
|
||||
// Pad user message if needed
|
||||
content = padContent(content, baseMessageSize/2)
|
||||
} else {
|
||||
role = schemas.ChatMessageRoleAssistant
|
||||
content = assistantResponses[rand.Intn(len(assistantResponses))]
|
||||
// Pad assistant message if needed
|
||||
content = padContent(content, baseMessageSize)
|
||||
}
|
||||
|
||||
inputHistory[i] = schemas.ChatMessage{
|
||||
Role: role,
|
||||
Content: &schemas.ChatMessageContent{
|
||||
ContentStr: &content,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Generate output message
|
||||
outputContent := assistantResponses[rand.Intn(len(assistantResponses))]
|
||||
outputContent = padContent(outputContent, baseMessageSize)
|
||||
|
||||
outputMessage := schemas.ChatMessage{
|
||||
Role: schemas.ChatMessageRoleAssistant,
|
||||
Content: &schemas.ChatMessageContent{
|
||||
ContentStr: &outputContent,
|
||||
},
|
||||
}
|
||||
|
||||
// Serialize to JSON
|
||||
inputHistoryJSON, _ := json.Marshal(inputHistory)
|
||||
outputMessageJSON, _ := json.Marshal(outputMessage)
|
||||
|
||||
// Token usage
|
||||
promptTokens := rand.Intn(2000) + 500
|
||||
completionTokens := rand.Intn(1500) + 300
|
||||
totalTokens := promptTokens + completionTokens
|
||||
|
||||
tokenUsage := schemas.BifrostLLMUsage{
|
||||
PromptTokens: promptTokens,
|
||||
CompletionTokens: completionTokens,
|
||||
TotalTokens: totalTokens,
|
||||
}
|
||||
tokenUsageJSON, _ := json.Marshal(tokenUsage)
|
||||
|
||||
// Generate latency and cost
|
||||
latency := float64(rand.Intn(3000)+100) / 1000.0 // 0.1 to 3.1 seconds
|
||||
cost := float64(totalTokens) * 0.00001 // Rough estimate
|
||||
|
||||
// Create content summary for search
|
||||
contentSummary := fmt.Sprintf("%s %s",
|
||||
userPrompts[rand.Intn(len(userPrompts))],
|
||||
assistantResponses[rand.Intn(len(assistantResponses))][:min(200, len(assistantResponses[0]))])
|
||||
|
||||
// Generate log entry
|
||||
log := logstore.Log{
|
||||
ID: fmt.Sprintf("log-%d-%d", timestamp.Unix(), index),
|
||||
Timestamp: timestamp,
|
||||
Object: object,
|
||||
Provider: provider,
|
||||
Model: model,
|
||||
Status: status,
|
||||
Stream: rand.Float32() > 0.5,
|
||||
InputHistory: string(inputHistoryJSON),
|
||||
OutputMessage: string(outputMessageJSON),
|
||||
TokenUsage: string(tokenUsageJSON),
|
||||
Latency: &latency,
|
||||
Cost: &cost,
|
||||
PromptTokens: promptTokens,
|
||||
CompletionTokens: completionTokens,
|
||||
TotalTokens: totalTokens,
|
||||
ContentSummary: contentSummary,
|
||||
CreatedAt: timestamp,
|
||||
NumberOfRetries: rand.Intn(3),
|
||||
FallbackIndex: rand.Intn(2),
|
||||
}
|
||||
|
||||
// Add error details for failed requests
|
||||
if status == "error" {
|
||||
errorDetails := schemas.BifrostError{
|
||||
Error: &schemas.ErrorField{
|
||||
Message: "Rate limit exceeded",
|
||||
},
|
||||
Type: bifrost.Ptr("rate_limit_error"),
|
||||
}
|
||||
errorJSON, _ := json.Marshal(errorDetails)
|
||||
log.ErrorDetails = string(errorJSON)
|
||||
}
|
||||
|
||||
// Assign metadata: all invalid when --invalid-metadata is set, otherwise ~15% valid.
|
||||
if *invalidMeta {
|
||||
log.Metadata = new(invalidMetadataValues[rand.Intn(len(invalidMetadataValues))])
|
||||
} else if rand.Float32() < 0.15 {
|
||||
log.Metadata = new(validMetadataValues[rand.Intn(len(validMetadataValues))])
|
||||
}
|
||||
|
||||
// Randomly add selected key and virtual key
|
||||
if rand.Float32() > 0.3 {
|
||||
log.SelectedKeyID = fmt.Sprintf("key-%d", rand.Intn(100))
|
||||
log.SelectedKeyName = fmt.Sprintf("API Key %d", rand.Intn(100))
|
||||
}
|
||||
|
||||
if rand.Float32() > 0.5 {
|
||||
virtualKeyID := fmt.Sprintf("vkey-%d", rand.Intn(50))
|
||||
virtualKeyName := fmt.Sprintf("Virtual Key %d", rand.Intn(50))
|
||||
log.VirtualKeyID = &virtualKeyID
|
||||
log.VirtualKeyName = &virtualKeyName
|
||||
}
|
||||
|
||||
return log
|
||||
}
|
||||
|
||||
func generateMCPLog(index int) logstore.MCPToolLog {
|
||||
toolIdx := rand.Intn(len(mcpToolNames))
|
||||
toolName := mcpToolNames[toolIdx]
|
||||
serverLabel := mcpServerLabels[toolIdx]
|
||||
status := statuses[rand.Intn(len(statuses))]
|
||||
|
||||
timestamp := time.Now().Add(-time.Duration(rand.Intn(90*24)) * time.Hour)
|
||||
|
||||
// Serialize arguments
|
||||
args := mcpToolArguments[toolIdx]
|
||||
argsJSON, _ := json.Marshal(args)
|
||||
|
||||
// Serialize result (only for non-error statuses)
|
||||
var resultJSON []byte
|
||||
if status != "error" {
|
||||
result := mcpToolResults[toolIdx]
|
||||
resultJSON, _ = json.Marshal(result)
|
||||
}
|
||||
|
||||
latency := float64(rand.Intn(2000)+50) / 1000.0 // 50ms to 2050ms
|
||||
cost := float64(rand.Intn(10)+1) * 0.001 // $0.001 to $0.01
|
||||
|
||||
log := logstore.MCPToolLog{
|
||||
ID: fmt.Sprintf("mcp-%d-%d", timestamp.Unix(), index),
|
||||
Timestamp: timestamp,
|
||||
ToolName: toolName,
|
||||
ServerLabel: serverLabel,
|
||||
Status: status,
|
||||
Arguments: string(argsJSON),
|
||||
Result: string(resultJSON),
|
||||
Latency: &latency,
|
||||
Cost: &cost,
|
||||
CreatedAt: timestamp,
|
||||
}
|
||||
|
||||
// Add error details for failed requests
|
||||
if status == "error" {
|
||||
errorDetails := schemas.BifrostError{
|
||||
Error: &schemas.ErrorField{
|
||||
Message: mcpErrorMessages[rand.Intn(len(mcpErrorMessages))],
|
||||
},
|
||||
Type: bifrost.Ptr("tool_execution_error"),
|
||||
}
|
||||
errorJSON, _ := json.Marshal(errorDetails)
|
||||
log.ErrorDetails = string(errorJSON)
|
||||
}
|
||||
|
||||
// Assign metadata: all invalid when --invalid-metadata is set, otherwise ~15% valid.
|
||||
if *invalidMeta {
|
||||
log.Metadata = invalidMetadataValues[rand.Intn(len(invalidMetadataValues))]
|
||||
} else if rand.Float32() < 0.15 {
|
||||
log.Metadata = validMetadataValues[rand.Intn(len(validMetadataValues))]
|
||||
}
|
||||
|
||||
// ~50% linked to an LLM request
|
||||
if rand.Float32() < 0.5 {
|
||||
llmReqID := fmt.Sprintf("log-%d-%d", timestamp.Unix(), rand.Intn(1000))
|
||||
log.LLMRequestID = &llmReqID
|
||||
}
|
||||
|
||||
// ~70% have a virtual key
|
||||
if rand.Float32() < 0.7 {
|
||||
vkID := fmt.Sprintf("vkey-%d", rand.Intn(50))
|
||||
vkName := fmt.Sprintf("Virtual Key %d", rand.Intn(50))
|
||||
log.VirtualKeyID = &vkID
|
||||
log.VirtualKeyName = &vkName
|
||||
}
|
||||
|
||||
return log
|
||||
}
|
||||
|
||||
// padContent adds additional content to reach target size
|
||||
func padContent(content string, targetSize int) string {
|
||||
currentSize := len(content)
|
||||
if currentSize >= targetSize {
|
||||
return content
|
||||
}
|
||||
|
||||
// Add padding text that looks somewhat realistic
|
||||
paddingTexts := []string{
|
||||
"\n\nAdditional context: This explanation can be extended with more details about implementation specifics, edge cases, and advanced use cases.",
|
||||
"\n\nFor more information, consider exploring related topics such as system design patterns, performance optimization techniques, and scalability considerations.",
|
||||
"\n\nReal-world applications of this concept include enterprise systems, distributed architectures, cloud-native applications, and microservices-based solutions.",
|
||||
"\n\nBest practices suggest considering factors like maintainability, testability, documentation, error handling, logging, monitoring, and security throughout the development lifecycle.",
|
||||
"\n\nWhen implementing this in production environments, pay attention to resource management, connection pooling, caching strategies, rate limiting, and graceful degradation.",
|
||||
}
|
||||
|
||||
for len(content) < targetSize {
|
||||
padding := paddingTexts[rand.Intn(len(paddingTexts))]
|
||||
content += padding
|
||||
|
||||
// Add repeated detailed explanations to fill space
|
||||
if len(content) < targetSize {
|
||||
content += fmt.Sprintf("\n\nDetailed analysis point %d: ", rand.Intn(100))
|
||||
content += "In depth exploration of technical considerations, architectural decisions, implementation details, performance characteristics, scalability factors, and operational requirements. "
|
||||
|
||||
// Calculate remaining space and repeat count
|
||||
remaining := targetSize - len(content)
|
||||
if remaining > 0 {
|
||||
repeatUnit := "This provides comprehensive coverage of the topic with extensive examples and use cases. "
|
||||
repeatCount := remaining / len(repeatUnit)
|
||||
if repeatCount > 0 {
|
||||
content += strings.Repeat(repeatUnit, repeatCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate to exact target size if we went over
|
||||
if len(content) > targetSize {
|
||||
return content[:targetSize]
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
3
tests/scripts/migration-checker/go.mod
Normal file
3
tests/scripts/migration-checker/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/maximhq/bifrost/tests/scripts/migration-checker
|
||||
|
||||
go 1.26.2
|
||||
423
tests/scripts/migration-checker/main.go
Normal file
423
tests/scripts/migration-checker/main.go
Normal file
@@ -0,0 +1,423 @@
|
||||
// Package main provides a tool to validate migration table creation order
|
||||
// by checking that dependent tables (with foreign keys) are created after
|
||||
// the tables they reference.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TableDependency represents a foreign key relationship where
|
||||
// Table has a FK column that references DependsOn
|
||||
type TableDependency struct {
|
||||
Table string // Table that has the FK column
|
||||
DependsOn string // Table being referenced (must be created first)
|
||||
Field string // Field name with the FK
|
||||
SourceFile string // Source file where this is defined
|
||||
}
|
||||
|
||||
// MigrationAction represents a table creation or column addition in migrations
|
||||
type MigrationAction struct {
|
||||
MigrationID string
|
||||
ActionType string // "CreateTable" or "AddColumn"
|
||||
Table string
|
||||
Column string // Only for AddColumn
|
||||
Order int // Order within migrations.go
|
||||
Line int // Line number in file
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Default paths relative to where the script is run from
|
||||
migrationsPath := "framework/configstore/migrations.go"
|
||||
tablesDir := "framework/configstore/tables"
|
||||
|
||||
// Allow overriding via command line args
|
||||
if len(os.Args) > 1 {
|
||||
migrationsPath = os.Args[1]
|
||||
}
|
||||
if len(os.Args) > 2 {
|
||||
tablesDir = os.Args[2]
|
||||
}
|
||||
|
||||
// Parse table definitions to get FK dependencies
|
||||
dependencies, err := parseTableDefinitions(tablesDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing table definitions: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Parse migrations to get table creation order
|
||||
actions, err := parseMigrationOrder(migrationsPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing migrations: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Validate dependencies
|
||||
violations := validateDependencies(dependencies, actions)
|
||||
|
||||
// Report results
|
||||
if len(violations) == 0 {
|
||||
fmt.Println("✓ All migration dependencies are satisfied!")
|
||||
fmt.Printf(" Checked %d table dependencies across %d migration actions\n", len(dependencies), len(actions))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
fmt.Printf("✗ Found %d dependency violation(s):\n\n", len(violations))
|
||||
for _, v := range violations {
|
||||
fmt.Println(v)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// parseTableDefinitions parses all Go files in the tables directory
|
||||
// and extracts foreign key relationships from GORM struct tags
|
||||
//
|
||||
// GORM FK relationships:
|
||||
// 1. Belongs-to: `Budget *TableBudget gorm:"foreignKey:BudgetID"`
|
||||
// - FK column (BudgetID) is on THIS table
|
||||
// - Referenced table (TableBudget) must be created FIRST
|
||||
//
|
||||
// 2. Has-many: `Keys []TableKey gorm:"foreignKey:ProviderID"`
|
||||
// - FK column (ProviderID) is on the CHILD table (TableKey)
|
||||
// - THIS table (parent) must be created FIRST
|
||||
// - We don't track this as a dependency because the parent comes first naturally
|
||||
func parseTableDefinitions(tablesDir string) ([]TableDependency, error) {
|
||||
var dependencies []TableDependency
|
||||
|
||||
// Table struct name to table name mapping
|
||||
tableNames := make(map[string]string)
|
||||
|
||||
// First pass: collect all table name mappings
|
||||
files, err := filepath.Glob(filepath.Join(tablesDir, "*.go"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to glob tables dir: %w", err)
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
for _, file := range files {
|
||||
node, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse %s: %w", file, err)
|
||||
}
|
||||
|
||||
// Find struct types and their TableName methods
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
// Look for TableName() methods
|
||||
if funcDecl, ok := n.(*ast.FuncDecl); ok {
|
||||
if funcDecl.Name.Name == "TableName" && funcDecl.Recv != nil {
|
||||
// Get the receiver type name
|
||||
if len(funcDecl.Recv.List) > 0 {
|
||||
if ident, ok := funcDecl.Recv.List[0].Type.(*ast.Ident); ok {
|
||||
structName := ident.Name
|
||||
// Extract the table name from the return statement
|
||||
if funcDecl.Body != nil {
|
||||
for _, stmt := range funcDecl.Body.List {
|
||||
if ret, ok := stmt.(*ast.ReturnStmt); ok {
|
||||
if len(ret.Results) > 0 {
|
||||
if lit, ok := ret.Results[0].(*ast.BasicLit); ok {
|
||||
tableName := strings.Trim(lit.Value, `"`)
|
||||
tableNames[structName] = tableName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Second pass: find foreign key relationships
|
||||
// Pattern to match GORM foreignKey tags
|
||||
fkPattern := regexp.MustCompile(`foreignKey:(\w+)`)
|
||||
|
||||
for _, file := range files {
|
||||
node, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
typeSpec, ok := n.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
structType, ok := typeSpec.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
structName := typeSpec.Name.Name
|
||||
if !strings.HasPrefix(structName, "Table") && structName != "SessionsTable" {
|
||||
return true
|
||||
}
|
||||
|
||||
currentTableName := tableNames[structName]
|
||||
if currentTableName == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check each field for FK relationships
|
||||
for _, field := range structType.Fields.List {
|
||||
if field.Tag == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
tag := field.Tag.Value
|
||||
|
||||
// Skip fields that are not stored in DB
|
||||
if strings.Contains(tag, `gorm:"-"`) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Look for foreignKey in gorm tag
|
||||
fkMatches := fkPattern.FindStringSubmatch(tag)
|
||||
if len(fkMatches) < 2 {
|
||||
continue
|
||||
}
|
||||
fkColumn := fkMatches[1]
|
||||
|
||||
// Get field name
|
||||
fieldName := ""
|
||||
if len(field.Names) > 0 {
|
||||
fieldName = field.Names[0].Name
|
||||
}
|
||||
|
||||
// Determine the type of relationship based on field type
|
||||
var refTableStruct string
|
||||
isBelongsTo := false
|
||||
|
||||
switch t := field.Type.(type) {
|
||||
case *ast.StarExpr:
|
||||
// Pointer type: *TableBudget - this is a "belongs-to" relationship
|
||||
// The FK column is on THIS table, referencing the other table
|
||||
if ident, ok := t.X.(*ast.Ident); ok {
|
||||
refTableStruct = ident.Name
|
||||
isBelongsTo = true
|
||||
}
|
||||
case *ast.ArrayType, *ast.SliceExpr:
|
||||
// Slice type: []TableKey - this is a "has-many" relationship
|
||||
// The FK column is on the CHILD table, not on this table
|
||||
// Parent must be created first, which is natural order
|
||||
// We don't need to track this as a dependency
|
||||
continue
|
||||
case *ast.Ident:
|
||||
// Direct type reference
|
||||
refTableStruct = t.Name
|
||||
isBelongsTo = true
|
||||
}
|
||||
|
||||
// Only track belongs-to relationships where THIS table has the FK column
|
||||
if !isBelongsTo || refTableStruct == "" || !strings.HasPrefix(refTableStruct, "Table") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify the FK column exists on this struct
|
||||
hasFKColumn := false
|
||||
for _, f := range structType.Fields.List {
|
||||
if len(f.Names) > 0 && f.Names[0].Name == fkColumn {
|
||||
hasFKColumn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If the FK column is on this table, it's a belongs-to relationship
|
||||
// The referenced table must be created before this table
|
||||
if hasFKColumn {
|
||||
refTableName := tableNames[refTableStruct]
|
||||
if refTableName != "" {
|
||||
dependencies = append(dependencies, TableDependency{
|
||||
Table: currentTableName,
|
||||
DependsOn: refTableName,
|
||||
Field: fieldName,
|
||||
SourceFile: filepath.Base(file),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return dependencies, nil
|
||||
}
|
||||
|
||||
// parseMigrationOrder parses migrations.go and extracts the order of table creations
|
||||
func parseMigrationOrder(migrationsPath string) ([]MigrationAction, error) {
|
||||
var actions []MigrationAction
|
||||
|
||||
content, err := os.ReadFile(migrationsPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read migrations file: %w", err)
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
node, err := parser.ParseFile(fset, migrationsPath, content, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse migrations file: %w", err)
|
||||
}
|
||||
|
||||
// Track the current migration function being processed
|
||||
currentMigration := ""
|
||||
order := 0
|
||||
|
||||
// Table struct to table name mapping (simplified)
|
||||
tableMapping := map[string]string{
|
||||
"TableConfigHash": "config_hashes",
|
||||
"TableBudget": "governance_budgets",
|
||||
"TableRateLimit": "governance_rate_limits",
|
||||
"TableProvider": "config_providers",
|
||||
"TableKey": "config_keys",
|
||||
"TableModel": "config_models",
|
||||
"TableOauthConfig": "oauth_configs",
|
||||
"TableOauthToken": "oauth_tokens",
|
||||
"TableMCPClient": "config_mcp_clients",
|
||||
"TableClientConfig": "config_client",
|
||||
"TableEnvKey": "config_env_keys",
|
||||
"TableVectorStoreConfig": "config_vector_stores",
|
||||
"TableLogStoreConfig": "config_log_stores",
|
||||
"TableCustomer": "governance_customers",
|
||||
"TableTeam": "governance_teams",
|
||||
"TableVirtualKey": "governance_virtual_keys",
|
||||
"TableGovernanceConfig": "governance_configs",
|
||||
"TableModelPricing": "model_pricing",
|
||||
"TablePlugin": "plugins",
|
||||
"TableFrameworkConfig": "framework_configs",
|
||||
"TableVirtualKeyProviderConfig": "governance_virtual_key_provider_configs",
|
||||
"TableVirtualKeyMCPConfig": "governance_virtual_key_mcp_configs",
|
||||
"SessionsTable": "sessions",
|
||||
"TableDistributedLock": "distributed_locks",
|
||||
"TableModelConfig": "model_configs",
|
||||
"TableRoutingRule": "routing_rules",
|
||||
}
|
||||
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
// Track function declarations for migration IDs
|
||||
if funcDecl, ok := n.(*ast.FuncDecl); ok {
|
||||
if strings.HasPrefix(funcDecl.Name.Name, "migration") {
|
||||
currentMigration = funcDecl.Name.Name
|
||||
}
|
||||
}
|
||||
|
||||
// Look for CreateTable calls
|
||||
if call, ok := n.(*ast.CallExpr); ok {
|
||||
if sel, ok := call.Fun.(*ast.SelectorExpr); ok {
|
||||
if sel.Sel.Name == "CreateTable" {
|
||||
// Extract the table type
|
||||
if len(call.Args) > 0 {
|
||||
if unary, ok := call.Args[0].(*ast.UnaryExpr); ok {
|
||||
if comp, ok := unary.X.(*ast.CompositeLit); ok {
|
||||
if sel, ok := comp.Type.(*ast.SelectorExpr); ok {
|
||||
structName := sel.Sel.Name
|
||||
if tableName, exists := tableMapping[structName]; exists {
|
||||
pos := fset.Position(call.Pos())
|
||||
actions = append(actions, MigrationAction{
|
||||
MigrationID: currentMigration,
|
||||
ActionType: "CreateTable",
|
||||
Table: tableName,
|
||||
Order: order,
|
||||
Line: pos.Line,
|
||||
})
|
||||
order++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if sel.Sel.Name == "AddColumn" {
|
||||
// Extract column additions that might have FK constraints
|
||||
if len(call.Args) >= 2 {
|
||||
// First arg is table, second is column name
|
||||
if unary, ok := call.Args[0].(*ast.UnaryExpr); ok {
|
||||
if comp, ok := unary.X.(*ast.CompositeLit); ok {
|
||||
if sel, ok := comp.Type.(*ast.SelectorExpr); ok {
|
||||
structName := sel.Sel.Name
|
||||
if tableName, exists := tableMapping[structName]; exists {
|
||||
colName := ""
|
||||
if lit, ok := call.Args[1].(*ast.BasicLit); ok {
|
||||
colName = strings.Trim(lit.Value, `"`)
|
||||
}
|
||||
pos := fset.Position(call.Pos())
|
||||
actions = append(actions, MigrationAction{
|
||||
MigrationID: currentMigration,
|
||||
ActionType: "AddColumn",
|
||||
Table: tableName,
|
||||
Column: colName,
|
||||
Order: order,
|
||||
Line: pos.Line,
|
||||
})
|
||||
order++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
// validateDependencies checks if tables with FK dependencies are created after their referenced tables
|
||||
func validateDependencies(deps []TableDependency, actions []MigrationAction) []string {
|
||||
var violations []string
|
||||
|
||||
// Build a map of table -> first creation order
|
||||
tableCreationOrder := make(map[string]int)
|
||||
tableCreationLine := make(map[string]int)
|
||||
tableCreationMigration := make(map[string]string)
|
||||
|
||||
for _, action := range actions {
|
||||
if action.ActionType == "CreateTable" {
|
||||
if _, exists := tableCreationOrder[action.Table]; !exists {
|
||||
tableCreationOrder[action.Table] = action.Order
|
||||
tableCreationLine[action.Table] = action.Line
|
||||
tableCreationMigration[action.Table] = action.MigrationID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check each dependency
|
||||
for _, dep := range deps {
|
||||
depOrder, depExists := tableCreationOrder[dep.Table]
|
||||
refOrder, refExists := tableCreationOrder[dep.DependsOn]
|
||||
|
||||
// Skip if either table isn't created via migrations (might be handled elsewhere)
|
||||
if !depExists || !refExists {
|
||||
continue
|
||||
}
|
||||
|
||||
// The table with the FK (dep.Table) should be created AFTER the referenced table (dep.DependsOn)
|
||||
// Violation: dep.Table is created before dep.DependsOn
|
||||
if depOrder < refOrder {
|
||||
violations = append(violations, fmt.Sprintf(
|
||||
" - Table '%s' (line %d, %s) is created before '%s' (line %d, %s)\n"+
|
||||
" but '%s' has a FK column referencing '%s' via field '%s' (defined in %s)",
|
||||
dep.Table, tableCreationLine[dep.Table], tableCreationMigration[dep.Table],
|
||||
dep.DependsOn, tableCreationLine[dep.DependsOn], tableCreationMigration[dep.DependsOn],
|
||||
dep.Table, dep.DependsOn, dep.Field, dep.SourceFile,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Sort violations for consistent output
|
||||
sort.Strings(violations)
|
||||
|
||||
return violations
|
||||
}
|
||||
Reference in New Issue
Block a user