From 4fc0e71dd0749e18c7c8128890e3961f0e51022e Mon Sep 17 00:00:00 2001 From: tianzhou Date: Thu, 23 Oct 2025 17:56:54 +0800 Subject: [PATCH 1/2] chore: update after pg_query removed --- CLAUDE.md | 11 +- ir/README.md | 42 +-- ir/ir_integration_test.go | 673 -------------------------------------- testdata/dump/README.md | 24 -- 4 files changed, 7 insertions(+), 743 deletions(-) delete mode 100644 ir/ir_integration_test.go diff --git a/CLAUDE.md b/CLAUDE.md index f9b8dd0d..735b4db6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,9 +13,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co The tool is written in Go 1.24+ (toolchain go1.24.7) and uses: - Cobra for CLI commands -- embedded-postgres v1.29.0 for integration testing (no Docker required) +- embedded-postgres v1.29.0 for plan command (temporary instances) and testing (no Docker required) - pgx/v5 v5.7.5 for database connections -- pg_query_go/v6 v6.1.0 for SQL parsing - Supports PostgreSQL versions 14-17 Key differentiators: @@ -113,7 +112,7 @@ PGPASSWORD=testpwd1 **Database Integration**: Uses `pgx/v5` for database connections and `embedded-postgres` (v1.29.0) for both the plan command (temporary instances) and integration testing (no Docker required). -**SQL Parsing**: Uses `pg_query_go/v6` (libpg_query bindings) for limited SQL expression parsing within the inspector (e.g., view definitions, CHECK constraints). The parser module was removed in favor of the embedded-postgres approach. +**Inspector-Only Approach**: Both desired state (from user SQL files) and current state (from target database) are obtained through database inspection. The plan command spins up an embedded PostgreSQL instance, applies user SQL files, then inspects it to get the desired state IR. This eliminates the need for SQL parsing and ensures consistency. **Modular Architecture**: The IR package is a separate Go module that can be versioned and used independently. @@ -135,12 +134,6 @@ Note: Parser logic is no longer needed - both desired and current states come fr 2. Use **Validate with Database** skill to test queries against live PostgreSQL 3. Compare pg_dump output with pgschema output using workflows in **Validate with Database** skill -### Understanding SQL Syntax - -1. Consult **PostgreSQL Syntax Reference** skill to find grammar rules in gram.y -2. Understand how pg_query_go parse tree maps to grammar -3. Test parsing with real SQL using **Validate with Database** skill - ### Testing Changes 1. Use **Run Tests** skill for comprehensive testing workflows diff --git a/ir/README.md b/ir/README.md index 2f75d541..0d7ff23e 100644 --- a/ir/README.md +++ b/ir/README.md @@ -19,35 +19,6 @@ go get github.com/pgschema/pgschema/ir@ir/v0.1.0 ## Usage -### Parsing SQL Files - -```go -import "github.com/pgschema/pgschema/ir" - -// Parse SQL file into IR -content, err := os.ReadFile("schema.sql") -if err != nil { - log.Fatal(err) -} - -// Create a parser with default schema and no ignore config -parser := ir.NewParser("public", nil) -schema, err := parser.ParseSQL(string(content)) -if err != nil { - log.Fatal(err) -} - -// Access tables, views, functions, etc. -if publicSchema, ok := schema.GetSchema("public"); ok { - for tableName, table := range publicSchema.Tables { - fmt.Printf("Table: %s\n", tableName) - for _, column := range table.Columns { - fmt.Printf(" Column: %s %s\n", column.Name, column.DataType) - } - } -} -``` - ### Introspecting Live Database ```go @@ -105,11 +76,11 @@ newSchema := // ... parse or introspect new schema ## Key Features -- **SQL Parsing**: Supports PostgreSQL DDL via libpg_query bindings - **Database Introspection**: Query live databases using optimized SQL queries -- **Normalization**: Consistent representation regardless of input source +- **Normalization**: Consistent representation from PostgreSQL system catalogs - **Rich Type System**: Full support for PostgreSQL data types and constraints - **Concurrent Safe**: Thread-safe access to schema data structures +- **Embedded Testing**: Use embedded PostgreSQL for testing without Docker (see `ParseSQLForTest` in testutil.go) ## Schema Object Types @@ -167,14 +138,11 @@ tables, err := q.GetTables(ctx, "public") ## Testing ```bash -# Unit tests only (fast, no Docker required) -go test -short -v ./... - -# Integration tests (requires Docker for testcontainers) +# Run all tests (uses embedded PostgreSQL, no Docker required) go test -v ./... -# Specific integration tests -go test -v ./ -run "TestIRIntegration_Employee" +# Skip integration tests (faster) +go test -short -v ./... ``` ## Versioning diff --git a/ir/ir_integration_test.go b/ir/ir_integration_test.go deleted file mode 100644 index 729d58ba..00000000 --- a/ir/ir_integration_test.go +++ /dev/null @@ -1,673 +0,0 @@ -package ir - -import ( - "context" - "encoding/json" - "fmt" - "os" - "strings" - "testing" -) - -// IR Integration Tests -// These comprehensive integration tests verify the entire IR workflow by comparing -// IR representations from two different sources: -// 1. Database inspection (pgdump.sql → database → ir/inspector → IR) -// 2. SQL parsing (pgschema.sql → ir/parser → IR) -// This ensures our pgschema output accurately represents the original database schema -// -// Test Workflow: -// pgdump.sql → Database → [INSPECTOR] → IR -// ↓ -// Semantic Equivalence? -// ↑ -// pgschema.sql → [PARSER] → IR -// -// Both paths should produce semantically equivalent IR representations - -func TestIRIntegration_Employee(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - // Test complete IR workflow integration for employee dataset - runIRIntegrationTest(t, "employee") -} - -func TestIRIntegration_Bytebase(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - // Test complete IR workflow integration for bytebase dataset - runIRIntegrationTest(t, "bytebase") -} - -func TestIRIntegration_Sakila(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - // Test complete IR workflow integration for sakila dataset - runIRIntegrationTest(t, "sakila") -} - -// runIRIntegrationTest performs comprehensive IR workflow integration testing -// This function validates the complete IR workflow by comparing representations -// from database inspection and SQL parsing to ensure semantic equivalence -// -// Integration Test Flow: -// 1. Load pgdump.sql into PostgreSQL container -// 2. Build IR from database using ir/inspector (database inspection) -// 3. Parse pgschema.sql into IR using ir/parser (SQL parsing) -// 4. Compare both IR representations for semantic equivalence -func runIRIntegrationTest(t *testing.T, testDataDir string) { - ctx := context.Background() - - // Start PostgreSQL container - containerInfo := setupPostgresContainer(ctx, t) - defer containerInfo.terminate(ctx, t) - - // Get database connection - db := containerInfo.Conn - - // FIRST IR: Load pgdump.sql and build IR from database inspection - t.Logf("=== FIRST IR GENERATION: pgdump.sql -> database -> ir/inspector -> IR ===") - - pgdumpPath := fmt.Sprintf("../testdata/dump/%s/pgdump.sql", testDataDir) - pgdumpContent, err := os.ReadFile(pgdumpPath) - if err != nil { - t.Fatalf("Failed to read %s: %v", pgdumpPath, err) - } - - // Execute pgdump.sql to populate database - _, err = db.ExecContext(ctx, string(pgdumpContent)) - if err != nil { - t.Fatalf("Failed to execute pgdump.sql: %v", err) - } - - // Build IR from database inspection using ir/inspector - inspector := NewInspector(db, nil) - dbIR, err := inspector.BuildIR(ctx, "public") - if err != nil { - t.Fatalf("Failed to build IR from database: %v", err) - } - - // SECOND IR: Parse pgschema.sql directly into IR - t.Logf("=== SECOND IR GENERATION: pgschema.sql -> ir/parser -> IR ===") - - pgschemaPath := fmt.Sprintf("../testdata/dump/%s/pgschema.sql", testDataDir) - pgschemaContent, err := os.ReadFile(pgschemaPath) - if err != nil { - t.Fatalf("Failed to read %s: %v", pgschemaPath, err) - } - - // Parse pgschema.sql into IR using ir/parser - parser := NewParser("public", nil) - parserIR, err := parser.ParseSQL(string(pgschemaContent)) - if err != nil { - t.Fatalf("Failed to parse pgschema.sql into IR: %v", err) - } - - // INTEGRATION VALIDATION: Compare both IR representations for semantic equivalence - t.Logf("=== IR INTEGRATION VALIDATION ===") - - // Perform comprehensive IR comparison - dbInput := IRComparisonInput{ - IR: dbIR, - Description: "Database IR (pgdump.sql → database → ir/inspector → IR)", - } - parserInput := IRComparisonInput{ - IR: parserIR, - Description: "Parser IR (pgschema.sql → ir/parser → IR)", - } - - compareIRSemanticEquivalence(t, dbInput, parserInput) - - // Save debug output on failure - if t.Failed() { - saveIRDebugFiles(t, testDataDir, dbInput, parserInput) - } - - t.Logf("=== IR INTEGRATION TEST COMPLETED ===") -} - -// IRComparisonInput represents the input for semantic IR comparison -type IRComparisonInput struct { - IR *IR - Description string // e.g., "Database IR (from pgdump.sql -> database -> ir/inspector -> IR)" -} - -// compareIRSemanticEquivalence performs enhanced semantic comparison between two IR representations -// This function focuses on semantic equivalence rather than exact structural matching -func compareIRSemanticEquivalence(t *testing.T, input1, input2 IRComparisonInput) { - t.Logf("=== SEMANTIC EQUIVALENCE ANALYSIS ===") - t.Logf("Comparing: %s", input1.Description) - t.Logf("With: %s", input2.Description) - - // Log detailed object counts first - logDetailedObjectCounts(t, input1, input2) - - // Compare top-level schema counts - if len(input1.IR.Schemas) != len(input2.IR.Schemas) { - t.Errorf("Schema count mismatch: %s has %d, %s has %d", - input1.Description, len(input1.IR.Schemas), - input2.Description, len(input2.IR.Schemas)) - } - - // Compare each schema for semantic equivalence - for schemaName, schema1 := range input1.IR.Schemas { - schema2, exists := input2.IR.Schemas[schemaName] - if !exists { - t.Errorf("Schema %s not found in %s", schemaName, input2.Description) - continue - } - - t.Logf("--- Comparing schema: %s ---", schemaName) - compareDBSchemaSemanticEquivalence(t, schemaName, schema1, schema2, input1.Description, input2.Description) - } - - // Check for extra schemas in second IR - for schemaName := range input2.IR.Schemas { - if _, exists := input1.IR.Schemas[schemaName]; !exists { - t.Errorf("Unexpected schema %s found in %s", schemaName, input2.Description) - } - } - - t.Logf("=== SEMANTIC EQUIVALENCE ANALYSIS COMPLETED ===") -} - -// logDetailedObjectCounts logs detailed object counts for both IR inputs -func logDetailedObjectCounts(t *testing.T, input1, input2 IRComparisonInput) { - t.Logf("%s has %d schemas", input1.Description, len(input1.IR.Schemas)) - t.Logf("%s has %d schemas", input2.Description, len(input2.IR.Schemas)) - - // Detailed object count logging - for schemaName, schema1 := range input1.IR.Schemas { - schema2 := input2.IR.Schemas[schemaName] - if schema2 != nil { - indexCount1 := countTableLevelIndexes(schema1) - indexCount2 := countTableLevelIndexes(schema2) - t.Logf("Schema '%s': %s[tables=%d, views=%d, funcs=%d, seqs=%d, indexes=%d] vs %s[tables=%d, views=%d, funcs=%d, seqs=%d, indexes=%d]", - schemaName, - getShortDescription(input1.Description), len(schema1.Tables), len(schema1.Views), len(schema1.Functions), len(schema1.Sequences), indexCount1, - getShortDescription(input2.Description), len(schema2.Tables), len(schema2.Views), len(schema2.Functions), len(schema2.Sequences), indexCount2) - } - } -} - -// getShortDescription extracts a short identifier from a long description -func getShortDescription(description string) string { - if strings.Contains(description, "Database IR") { - return "DB" - } - if strings.Contains(description, "Parser IR") { - return "Parser" - } - return "IR" -} - -// compareDBSchemaSemanticEquivalence compares two DBSchema objects for semantic equivalence -func compareDBSchemaSemanticEquivalence(t *testing.T, schemaName string, schema1, schema2 *Schema, desc1, desc2 string) { - // Compare tables (focus on BASE tables for semantic equivalence) - baseTables1 := make(map[string]*Table) - baseTables2 := make(map[string]*Table) - - for name, table := range schema1.Tables { - if table.Type == TableTypeBase { - baseTables1[name] = table - } - } - for name, table := range schema2.Tables { - if table.Type == TableTypeBase { - baseTables2[name] = table - } - } - - if len(baseTables1) != len(baseTables2) { - t.Errorf("Schema %s: base table count difference: %s has %d, %s has %d (may be due to partition table handling differences)", - schemaName, desc1, len(baseTables1), desc2, len(baseTables2)) - } - - // Compare each base table - for tableName, table1 := range baseTables1 { - table2, exists := baseTables2[tableName] - if !exists { - t.Errorf("Schema %s: base table %s not found in %s", schemaName, tableName, desc2) - continue - } - - compareTableSemanticEquivalence(t, schemaName, tableName, table1, table2, desc1, desc2) - } - - // Compare views - compareViewsSemanticEquivalence(t, schemaName, schema1.Views, schema2.Views, desc1, desc2) - - // Compare functions - compareFunctionsSemanticEquivalence(t, schemaName, schema1.Functions, schema2.Functions, desc1, desc2) - - // Compare sequences - compareSequencesSemanticEquivalence(t, schemaName, schema1.Sequences, schema2.Sequences, desc1, desc2) - - // Compare indexes at table level - compareTableLevelIndexesSemanticEquivalence(t, schemaName, schema1, schema2, desc1, desc2) - - // Log comparison results with table-level index counts - indexCount1 := countTableLevelIndexes(schema1) - indexCount2 := countTableLevelIndexes(schema2) - t.Logf("Schema %s semantic comparison: tables=%d/%d, views=%d/%d, functions=%d/%d, sequences=%d/%d, indexes=%d/%d", - schemaName, - len(baseTables2), len(baseTables1), - len(schema2.Views), len(schema1.Views), - len(schema2.Functions), len(schema1.Functions), - len(schema2.Sequences), len(schema1.Sequences), - indexCount2, indexCount1) -} - -// compareTableSemanticEquivalence compares two tables for semantic equivalence -func compareTableSemanticEquivalence(t *testing.T, schemaName, tableName string, table1, table2 *Table, desc1, desc2 string) { - // Basic properties - if table1.Name != table2.Name { - t.Errorf("Table %s.%s: name mismatch: %s has %s, %s has %s", - schemaName, tableName, desc1, table1.Name, desc2, table2.Name) - } - - if table1.Schema != table2.Schema { - t.Errorf("Table %s.%s: schema mismatch: %s has %s, %s has %s", - schemaName, tableName, desc1, table1.Schema, desc2, table2.Schema) - } - - // Column count and semantic equivalence - if len(table1.Columns) != len(table2.Columns) { - t.Errorf("Table %s.%s: column count mismatch: %s has %d, %s has %d", - schemaName, tableName, desc1, len(table1.Columns), desc2, len(table2.Columns)) - } - - // Create maps for easier column comparison - columns1 := make(map[string]*Column) - columns2 := make(map[string]*Column) - - for _, col := range table1.Columns { - columns1[col.Name] = col - } - for _, col := range table2.Columns { - columns2[col.Name] = col - } - - // Compare each column semantically - for colName, col1 := range columns1 { - col2, exists := columns2[colName] - if !exists { - t.Errorf("Table %s.%s: column %s not found in %s", - schemaName, tableName, colName, desc2) - continue - } - - compareColumnSemanticEquivalence(t, schemaName, tableName, colName, col1, col2, desc1, desc2) - } - - // Log constraint differences - if len(table1.Constraints) != len(table2.Constraints) { - t.Errorf("Table %s.%s: constraint count difference: %s has %d, %s has %d", - schemaName, tableName, desc1, len(table1.Constraints), desc2, len(table2.Constraints)) - } - - // Compare triggers - compareTriggersSemanticEquivalence(t, schemaName, tableName, table1.Triggers, table2.Triggers, desc1, desc2) -} - -// compareColumnSemanticEquivalence compares columns with focus on semantic equivalence -func compareColumnSemanticEquivalence(t *testing.T, schemaName, tableName, colName string, col1, col2 *Column, desc1, desc2 string) { - // Position should match - if col1.Position != col2.Position { - t.Errorf("Column %s.%s.%s: position mismatch: %s has %d, %s has %d", - schemaName, tableName, colName, desc1, col1.Position, desc2, col2.Position) - } - - // Data type should match - if col1.DataType != col2.DataType { - t.Errorf("Column %s.%s.%s: data type mismatch: %s has %s, %s has %s", - schemaName, tableName, colName, desc1, col1.DataType, desc2, col2.DataType) - } - - // Nullable - if col1.IsNullable != col2.IsNullable { - t.Errorf("Column %s.%s.%s: nullable difference: %s has %t, %s has %t (may be due to parsing limitations)", - schemaName, tableName, colName, desc1, col1.IsNullable, desc2, col2.IsNullable) - } - - // Default values - strict comparison - if !areDefaultValuesEqual(col1.DefaultValue, col2.DefaultValue) { - default1 := "NULL" - default2 := "NULL" - if col1.DefaultValue != nil { - default1 = *col1.DefaultValue - } - if col2.DefaultValue != nil { - default2 = *col2.DefaultValue - } - t.Errorf("Column %s.%s.%s: default value mismatch: %s has %q, %s has %q", - schemaName, tableName, colName, desc1, default1, desc2, default2) - } -} - -// areDefaultValuesEqual checks if default values are semantically equivalent -func areDefaultValuesEqual(val1, val2 *string) bool { - // Both nil - if val1 == nil && val2 == nil { - return true - } - - // One nil, one not - if (val1 == nil) != (val2 == nil) { - return false - } - - // Both not nil - normalize and compare semantically - normalized1 := normalizeDefaultValue(*val1) - normalized2 := normalizeDefaultValue(*val2) - return normalized1 == normalized2 -} - -// compareViewsSemanticEquivalence compares views for semantic equivalence -func compareViewsSemanticEquivalence(t *testing.T, schemaName string, views1, views2 map[string]*View, desc1, desc2 string) { - if len(views1) != len(views2) { - t.Errorf("Schema %s: view count difference: %s has %d, %s has %d", - schemaName, desc1, len(views1), desc2, len(views2)) - } - - for viewName := range views1 { - if _, exists := views2[viewName]; !exists { - t.Errorf("Schema %s: view %s not found in %s", schemaName, viewName, desc2) - } - } -} - -// compareFunctionsSemanticEquivalence compares functions for semantic equivalence -func compareFunctionsSemanticEquivalence(t *testing.T, schemaName string, funcs1, funcs2 map[string]*Function, desc1, desc2 string) { - if len(funcs1) != len(funcs2) { - t.Errorf("Schema %s: function count difference: %s has %d, %s has %d", - schemaName, desc1, len(funcs1), desc2, len(funcs2)) - } - - for funcName := range funcs1 { - if _, exists := funcs2[funcName]; !exists { - t.Errorf("Schema %s: function %s not found in %s", schemaName, funcName, desc2) - } - } -} - -// compareSequencesSemanticEquivalence compares sequences for semantic equivalence -func compareSequencesSemanticEquivalence(t *testing.T, schemaName string, seqs1, seqs2 map[string]*Sequence, desc1, desc2 string) { - if len(seqs1) != len(seqs2) { - t.Errorf("Schema %s: sequence count difference: %s has %d, %s has %d", - schemaName, desc1, len(seqs1), desc2, len(seqs2)) - } - - for seqName := range seqs1 { - if _, exists := seqs2[seqName]; !exists { - t.Errorf("Schema %s: sequence %s not found in %s", schemaName, seqName, desc2) - } - } -} - -// countTableLevelIndexes counts all indexes stored at table level within a schema -func countTableLevelIndexes(schema *Schema) int { - count := 0 - for _, table := range schema.Tables { - count += len(table.Indexes) - } - return count -} - -// compareTableLevelIndexesSemanticEquivalence compares indexes stored at table level -func compareTableLevelIndexesSemanticEquivalence(t *testing.T, schemaName string, schema1, schema2 *Schema, desc1, desc2 string) { - // Collect all indexes from tables in first schema - indexes1 := make(map[string]*Index) - for tableName, table := range schema1.Tables { - for indexName, index := range table.Indexes { - // Use table.index format as key to ensure uniqueness across tables - key := fmt.Sprintf("%s.%s", tableName, indexName) - indexes1[key] = index - } - } - - // Collect all indexes from tables in second schema - indexes2 := make(map[string]*Index) - for tableName, table := range schema2.Tables { - for indexName, index := range table.Indexes { - // Use table.index format as key to ensure uniqueness across tables - key := fmt.Sprintf("%s.%s", tableName, indexName) - indexes2[key] = index - } - } - - // Compare index counts - if len(indexes1) != len(indexes2) { - t.Errorf("Schema %s: table-level index count difference: %s has %d, %s has %d", - schemaName, desc1, len(indexes1), desc2, len(indexes2)) - } - - // Compare each index - for indexKey, index1 := range indexes1 { - index2, exists := indexes2[indexKey] - if !exists { - t.Errorf("Schema %s: table-level index %s not found in %s", schemaName, indexKey, desc2) - continue - } - - compareIndexSemanticEquivalence(t, schemaName, indexKey, index1, index2, desc1, desc2) - } - - // Check for extra indexes in second schema - for indexKey := range indexes2 { - if _, exists := indexes1[indexKey]; !exists { - t.Errorf("Schema %s: unexpected table-level index %s found in %s", schemaName, indexKey, desc2) - } - } -} - -// compareIndexSemanticEquivalence compares two indexes for semantic equivalence -func compareIndexSemanticEquivalence(t *testing.T, schemaName, indexName string, index1, index2 *Index, desc1, desc2 string) { - // Basic properties - if index1.Name != index2.Name { - t.Errorf("Index %s.%s: name mismatch: %s has %s, %s has %s", - schemaName, indexName, desc1, index1.Name, desc2, index2.Name) - } - - if index1.Schema != index2.Schema { - t.Errorf("Index %s.%s: schema mismatch: %s has %s, %s has %s", - schemaName, indexName, desc1, index1.Schema, desc2, index2.Schema) - } - - if index1.Table != index2.Table { - t.Errorf("Index %s.%s: table mismatch: %s has %s, %s has %s", - schemaName, indexName, desc1, index1.Table, desc2, index2.Table) - } - - // Index type and flags - if index1.Type != index2.Type { - t.Errorf("Index %s.%s: type difference: %s has %s, %s has %s (may be acceptable due to semantic differences)", - schemaName, indexName, desc1, index1.Type, desc2, index2.Type) - } - - isUnique1 := index1.Type == IndexTypeUnique || index1.Type == IndexTypePrimary - isUnique2 := index2.Type == IndexTypeUnique || index2.Type == IndexTypePrimary - if isUnique1 != isUnique2 { - t.Errorf("Index %s.%s: unique flag mismatch: %s has %t, %s has %t", - schemaName, indexName, desc1, isUnique1, desc2, isUnique2) - } - - if index1.Type == IndexTypePrimary != (index2.Type == IndexTypePrimary) { - t.Errorf("Index %s.%s: primary flag mismatch: %s has %t, %s has %t", - schemaName, indexName, desc1, index1.Type == IndexTypePrimary, desc2, index2.Type == IndexTypePrimary) - } - - if index1.IsPartial != index2.IsPartial { - t.Errorf("Index %s.%s: partial flag difference: %s has %t, %s has %t", - schemaName, indexName, desc1, index1.IsPartial, desc2, index2.IsPartial) - } - - // Index method - if index1.Method != index2.Method { - t.Errorf("Index %s.%s: method difference: %s has %s, %s has %s", - schemaName, indexName, desc1, index1.Method, desc2, index2.Method) - } - - // Column count - if len(index1.Columns) != len(index2.Columns) { - t.Errorf("Index %s.%s: column count mismatch: %s has %d, %s has %d", - schemaName, indexName, desc1, len(index1.Columns), desc2, len(index2.Columns)) - } - - // Compare columns semantically - columnsMap1 := make(map[int]*IndexColumn) - columnsMap2 := make(map[int]*IndexColumn) - - for _, col := range index1.Columns { - columnsMap1[col.Position] = col - } - for _, col := range index2.Columns { - columnsMap2[col.Position] = col - } - - for position, col1 := range columnsMap1 { - col2, exists := columnsMap2[position] - if !exists { - t.Errorf("Index %s.%s: column at position %d not found in %s", - schemaName, indexName, position, desc2) - continue - } - - compareIndexColumnSemanticEquivalence(t, schemaName, indexName, position, col1, col2, desc1, desc2) - } - - // Partial index WHERE clause - normalize for comparison - if index1.IsPartial || index2.IsPartial { - where1 := strings.TrimSpace(index1.Where) - where2 := strings.TrimSpace(index2.Where) - if where1 != where2 { - t.Errorf("Index %s.%s: WHERE clause difference: %s has %q, %s has %q (may be due to format differences)", - schemaName, indexName, desc1, where1, desc2, where2) - } - } -} - -// compareIndexColumnSemanticEquivalence compares index columns for semantic equivalence -func compareIndexColumnSemanticEquivalence(t *testing.T, schemaName, indexName string, position int, col1, col2 *IndexColumn, desc1, desc2 string) { - if col1.Name != col2.Name { - t.Errorf("Index %s.%s column at position %d: name mismatch: %s has %s, %s has %s", - schemaName, indexName, position, desc1, col1.Name, desc2, col2.Name) - } - - if col1.Position != col2.Position { - t.Errorf("Index %s.%s column %s: position mismatch: %s has %d, %s has %d", - schemaName, indexName, col1.Name, desc1, col1.Position, desc2, col2.Position) - } - - // Direction and operator may have variations - if col1.Direction != col2.Direction { - t.Errorf("Index %s.%s column %s: direction difference: %s has %s, %s has %s", - schemaName, indexName, col1.Name, desc1, col1.Direction, desc2, col2.Direction) - } - - if col1.Operator != col2.Operator { - t.Errorf("Index %s.%s column %s: operator difference: %s has %s, %s has %s", - schemaName, indexName, col1.Name, desc1, col1.Operator, desc2, col2.Operator) - } -} - -// compareTriggersSemanticEquivalence compares triggers for semantic equivalence -func compareTriggersSemanticEquivalence(t *testing.T, schemaName, tableName string, triggers1, triggers2 map[string]*Trigger, desc1, desc2 string) { - // Check trigger count - if len(triggers1) != len(triggers2) { - t.Errorf("Table %s.%s: trigger count difference: %s has %d, %s has %d", - schemaName, tableName, desc1, len(triggers1), desc2, len(triggers2)) - } - - // Compare each trigger - for triggerName, trigger1 := range triggers1 { - trigger2, exists := triggers2[triggerName] - if !exists { - t.Errorf("Table %s.%s: trigger %s not found in %s", - schemaName, tableName, triggerName, desc2) - continue - } - - compareTriggerSemanticEquivalence(t, schemaName, tableName, triggerName, trigger1, trigger2, desc1, desc2) - } - - // Check for extra triggers in second map - for triggerName := range triggers2 { - if _, exists := triggers1[triggerName]; !exists { - t.Errorf("Table %s.%s: unexpected trigger %s found in %s", - schemaName, tableName, triggerName, desc2) - } - } -} - -// compareTriggerSemanticEquivalence compares individual triggers for semantic equivalence -func compareTriggerSemanticEquivalence(t *testing.T, schemaName, tableName, triggerName string, trigger1, trigger2 *Trigger, desc1, desc2 string) { - // Compare basic properties - if trigger1.Name != trigger2.Name { - t.Errorf("Trigger %s.%s.%s: name mismatch: %s has %s, %s has %s", - schemaName, tableName, triggerName, desc1, trigger1.Name, desc2, trigger2.Name) - } - - if trigger1.Timing != trigger2.Timing { - t.Errorf("Trigger %s.%s.%s: timing mismatch: %s has %s, %s has %s", - schemaName, tableName, triggerName, desc1, trigger1.Timing, desc2, trigger2.Timing) - } - - if trigger1.Level != trigger2.Level { - t.Errorf("Trigger %s.%s.%s: level mismatch: %s has %s, %s has %s", - schemaName, tableName, triggerName, desc1, trigger1.Level, desc2, trigger2.Level) - } - - // Compare events - if len(trigger1.Events) != len(trigger2.Events) { - t.Errorf("Trigger %s.%s.%s: event count mismatch: %s has %d, %s has %d", - schemaName, tableName, triggerName, desc1, len(trigger1.Events), desc2, len(trigger2.Events)) - } else { - // Compare each event - for i, event1 := range trigger1.Events { - if i < len(trigger2.Events) && event1 != trigger2.Events[i] { - t.Errorf("Trigger %s.%s.%s: event %d mismatch: %s has %s, %s has %s", - schemaName, tableName, triggerName, i, desc1, event1, desc2, trigger2.Events[i]) - } - } - } - - // Compare function calls - this is the critical comparison - if trigger1.Function != trigger2.Function { - t.Errorf("Trigger %s.%s.%s: function mismatch: %s has %q, %s has %q", - schemaName, tableName, triggerName, desc1, trigger1.Function, desc2, trigger2.Function) - } - - // Compare conditions - if trigger1.Condition != trigger2.Condition { - t.Errorf("Trigger %s.%s.%s: condition mismatch: %s has %q, %s has %q", - schemaName, tableName, triggerName, desc1, trigger1.Condition, desc2, trigger2.Condition) - } -} - -// SaveIRDebugFiles saves IR representations to files for debugging -func saveIRDebugFiles(t *testing.T, testDataDir string, input1, input2 IRComparisonInput) { - // Save first IR - ir1Path := fmt.Sprintf("%s_ir1_debug.json", testDataDir) - if ir1JSON, err := json.MarshalIndent(input1.IR, "", " "); err == nil { - if err := os.WriteFile(ir1Path, ir1JSON, 0644); err == nil { - t.Logf("Debug: First IR (%s) written to %s", input1.Description, ir1Path) - } - } - - // Save second IR - ir2Path := fmt.Sprintf("%s_ir2_debug.json", testDataDir) - if ir2JSON, err := json.MarshalIndent(input2.IR, "", " "); err == nil { - if err := os.WriteFile(ir2Path, ir2JSON, 0644); err == nil { - t.Logf("Debug: Second IR (%s) written to %s", input2.Description, ir2Path) - } - } - - t.Logf("Debug files saved for detailed IR comparison analysis") -} diff --git a/testdata/dump/README.md b/testdata/dump/README.md index 67e40fe1..60d9f37a 100644 --- a/testdata/dump/README.md +++ b/testdata/dump/README.md @@ -67,27 +67,3 @@ Each test: ### Multi-File Dump Tests (`cmd/dump/multifile_test.go`) Tests the `--multi-file` functionality that outputs schema objects to separate files organized by type. - -### IR (Intermediate Representation) Tests (`internal/ir/ir_integration_test.go`) - -The test data validates the complete IR workflow by comparing two different paths to generate the same IR: - -**Two-Path Validation:** - -1. **Inspector Path**: Database → `ir/inspector` → IR - - Loads schema into a real PostgreSQL database (using testcontainers) - - Uses the Inspector to query the database and build IR from live schema metadata - - Represents the "ground truth" from actual PostgreSQL system catalogs - -2. **Parser Path**: `pgschema.sql` → `ir/parser` → IR - - Parses the `pgschema.sql` file directly into IR using SQL parser - - Tests the parser's ability to understand pgschema's output format - - Validates that pgschema output can be round-tripped back to IR - -**What's Tested:** - -- **Semantic Equivalence**: Both paths should produce semantically equivalent IR representations -- **Object Completeness**: All schema objects (tables, views, functions, sequences, indexes, types, policies) are captured -- **Metadata Accuracy**: Column types, constraints, defaults, and relationships are correctly represented -- **Dependency Resolution**: Object dependencies and topological sorting work correctly -- **Cross-Schema References**: Multi-schema scenarios (like tenant) properly handle schema qualification From 66e7520b49c492a272952b24cd13abe61a0ff200 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Thu, 23 Oct 2025 18:05:56 +0800 Subject: [PATCH 2/2] chore: remove ir release module and update doc --- .github/workflows/release-ir.yml | 136 ------------------------------- CLAUDE.md | 6 +- go.mod | 7 +- ir/README.md | 31 ++----- ir/VERSION | 1 - ir/go.mod | 21 ----- ir/go.sum | 47 ----------- 7 files changed, 10 insertions(+), 239 deletions(-) delete mode 100644 .github/workflows/release-ir.yml delete mode 100644 ir/VERSION delete mode 100644 ir/go.mod delete mode 100644 ir/go.sum diff --git a/.github/workflows/release-ir.yml b/.github/workflows/release-ir.yml deleted file mode 100644 index 83612844..00000000 --- a/.github/workflows/release-ir.yml +++ /dev/null @@ -1,136 +0,0 @@ -name: Release IR Package - -on: - push: - tags: - - "ir/v*.*.*" - paths-ignore: - - "**.md" - - "docs/**" - workflow_dispatch: - -permissions: - contents: write - -jobs: - test-and-release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: "1.24" - - - name: Read IR version - id: version - run: | - if [[ "${{ github.ref }}" == refs/tags/ir/v* ]]; then - # Extract version from tag - VERSION=${GITHUB_REF#refs/tags/ir/v} - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "tag_name=ir/v${VERSION}" >> $GITHUB_OUTPUT - else - # Read from VERSION file for manual dispatch - VERSION=$(cat ir/VERSION) - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "tag_name=ir/v${VERSION}" >> $GITHUB_OUTPUT - fi - echo "Version: ${VERSION}" - - - name: Verify go.mod version matches - run: | - IR_VERSION=$(cat ir/VERSION) - echo "IR VERSION file: ${IR_VERSION}" - echo "Workflow version: ${{ steps.version.outputs.version }}" - - - name: Test IR package - run: | - cd ir - go mod tidy - go test -v ./... - - - name: Test IR package can be built independently - run: | - cd ir - go build -v ./... - - - name: Test main project still works with IR changes - run: | - go mod tidy - go test -short -v ./... - go build -v . - - - name: Create/Update tag for manual dispatch - if: github.event_name == 'workflow_dispatch' - run: | - TAG_NAME="${{ steps.version.outputs.tag_name }}" - echo "Creating tag: ${TAG_NAME}" - - # Check if tag exists - if git tag -l | grep -q "^${TAG_NAME}$"; then - echo "Tag ${TAG_NAME} already exists, deleting it" - git tag -d ${TAG_NAME} - git push origin --delete ${TAG_NAME} || true - fi - - # Create new tag - git tag ${TAG_NAME} - git push origin ${TAG_NAME} - - - name: Create Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.version.outputs.tag_name }} - name: IR Package v${{ steps.version.outputs.version }} - body: | - ## IR Package Release v${{ steps.version.outputs.version }} - - This release publishes the `ir` package as a standalone Go module that can be imported by external projects. - - ### Installation - ```bash - go get github.com/pgschema/pgschema/ir@${{ steps.version.outputs.tag_name }} - ``` - - ### Usage - ```go - import "github.com/pgschema/pgschema/ir" - import "github.com/pgschema/pgschema/ir/queries" - ``` - - ### Features - - PostgreSQL schema parsing from SQL files - - Database introspection with optimized queries - - Normalized schema representation - - Support for tables, views, functions, procedures, types, and more - - See the [IR package documentation](https://pkg.go.dev/github.com/pgschema/pgschema/ir@${{ steps.version.outputs.tag_name }}) for detailed usage examples. - draft: false - prerelease: false - generate_release_notes: false - - - name: Wait for Go proxy to index - run: | - echo "Waiting for Go proxy to index the new version..." - sleep 30 - - # Try to fetch the module to trigger proxy indexing - TAG_NAME="${{ steps.version.outputs.tag_name }}" - echo "Attempting to fetch: github.com/pgschema/pgschema/ir@${TAG_NAME}" - - # This may fail initially but will trigger the proxy to fetch - go list -m github.com/pgschema/pgschema/ir@${TAG_NAME} || echo "Module not yet available in proxy (expected)" - - - name: Summary - run: | - echo "## Release Summary" >> $GITHUB_STEP_SUMMARY - echo "- **Package**: IR Package" >> $GITHUB_STEP_SUMMARY - echo "- **Version**: ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "- **Tag**: ${{ steps.version.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY - echo "- **Import**: \`go get github.com/pgschema/pgschema/ir@${{ steps.version.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY - echo "- **Documentation**: https://pkg.go.dev/github.com/pgschema/pgschema/ir@${{ steps.version.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 735b4db6..7f8f6ab0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,7 +85,7 @@ PGPASSWORD=testpwd1 - `root.go` - Main CLI setup with Cobra **Core Packages**: -- `ir/` - Intermediate Representation (IR) package - separate Go module +- `ir/` - Intermediate Representation (IR) package - Schema objects (tables, indexes, functions, procedures, triggers, policies, etc.) - Database inspector using pgx (queries pg_catalog for schema extraction) - Schema normalizer @@ -114,8 +114,6 @@ PGPASSWORD=testpwd1 **Inspector-Only Approach**: Both desired state (from user SQL files) and current state (from target database) are obtained through database inspection. The plan command spins up an embedded PostgreSQL instance, applies user SQL files, then inspects it to get the desired state IR. This eliminates the need for SQL parsing and ensures consistency. -**Modular Architecture**: The IR package is a separate Go module that can be versioned and used independently. - ## Common Development Workflows ### Adding New Schema Object Support @@ -191,7 +189,7 @@ The tool supports comprehensive PostgreSQL schema objects (see `ir/ir.go` for co - `main.go` - Entry point, loads .env and calls cmd.Execute() - `cmd/root.go` - Root CLI with global flags -**IR Package** (separate Go module at `./ir`): +**IR Package** (`./ir`): - `ir/ir.go` - Core IR data structures for all schema objects - `ir/inspector.go` - Database introspection using pgx (queries pg_catalog) - `ir/normalize.go` - Schema normalization (version-specific differences, type mappings) diff --git a/go.mod b/go.mod index 21da726b..31a6ebe8 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,9 @@ require ( github.com/google/go-cmp v0.7.0 github.com/jackc/pgx/v5 v5.7.5 github.com/joho/godotenv v1.5.1 - github.com/pgschema/pgschema/ir v0.0.0 + github.com/lib/pq v1.10.9 github.com/spf13/cobra v1.9.1 + golang.org/x/sync v0.17.0 ) require ( @@ -19,13 +20,9 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/lib/pq v1.10.9 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect golang.org/x/crypto v0.37.0 // indirect - golang.org/x/sync v0.17.0 // indirect golang.org/x/text v0.24.0 // indirect ) - -replace github.com/pgschema/pgschema/ir => ./ir diff --git a/ir/README.md b/ir/README.md index 0d7ff23e..9d611e25 100644 --- a/ir/README.md +++ b/ir/README.md @@ -3,18 +3,17 @@ [![Go Reference](https://pkg.go.dev/badge/github.com/pgschema/pgschema/ir.svg)](https://pkg.go.dev/github.com/pgschema/pgschema/ir) [![Go Report Card](https://goreportcard.com/badge/github.com/pgschema/pgschema/ir)](https://goreportcard.com/report/github.com/pgschema/pgschema/ir) -The `ir` package provides an Intermediate Representation for PostgreSQL database schemas. It can be used by external projects to parse SQL files, introspect live databases, and work with normalized schema representations. +The `ir` package provides an Intermediate Representation for PostgreSQL database schemas. It introspects live databases using PostgreSQL system catalogs and provides normalized schema representations. ## Installation -### Latest Version ```bash -go get github.com/pgschema/pgschema/ir +go get github.com/pgschema/pgschema ``` -### Specific Version -```bash -go get github.com/pgschema/pgschema/ir@ir/v0.1.0 +Then import the ir package: +```go +import "github.com/pgschema/pgschema/ir" ``` ## Usage @@ -145,25 +144,7 @@ go test -v ./... go test -short -v ./... ``` -## Versioning - -This package follows semantic versioning. Releases are tagged with the pattern `ir/v..`. - -### Release Process - -1. Update the version in `ir/VERSION` -2. Commit the change -3. Create and push a tag: `git tag ir/v0.1.0 && git push origin ir/v0.1.0` -4. Or trigger a manual release from the GitHub Actions workflow - -### Changelog - -#### v0.1.0 -- Initial standalone release of the IR package -- Support for PostgreSQL schema parsing and introspection -- Complete type system for tables, views, functions, procedures, types, and sequences - -## Version Compatibility +## Compatibility - **Go**: 1.24.0+ - **PostgreSQL**: 14, 15, 16, 17 diff --git a/ir/VERSION b/ir/VERSION deleted file mode 100644 index 6c6aa7cb..00000000 --- a/ir/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.1.0 \ No newline at end of file diff --git a/ir/go.mod b/ir/go.mod deleted file mode 100644 index 095ee2b9..00000000 --- a/ir/go.mod +++ /dev/null @@ -1,21 +0,0 @@ -module github.com/pgschema/pgschema/ir - -go 1.24.0 - -require ( - github.com/fergusstrange/embedded-postgres v1.29.0 - github.com/jackc/pgx/v5 v5.7.5 - github.com/lib/pq v1.10.9 - github.com/pganalyze/pg_query_go/v6 v6.1.0 - golang.org/x/sync v0.17.0 -) - -require ( - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/text v0.24.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect -) diff --git a/ir/go.sum b/ir/go.sum deleted file mode 100644 index 87882c4c..00000000 --- a/ir/go.sum +++ /dev/null @@ -1,47 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fergusstrange/embedded-postgres v1.29.0 h1:Uv8hdhoiaNMuH0w8UuGXDHr60VoAQPFdgx7Qf3bzXJM= -github.com/fergusstrange/embedded-postgres v1.29.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -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.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= -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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= -github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -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.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.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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=