Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 283b568adb | |||
| 122743ee43 | |||
| 91b6046b9b | |||
| 6f55505444 |
@@ -55,6 +55,7 @@ var (
|
|||||||
mergeSkipSequences bool
|
mergeSkipSequences bool
|
||||||
mergeSkipTables string // Comma-separated table names to skip
|
mergeSkipTables string // Comma-separated table names to skip
|
||||||
mergeVerbose bool
|
mergeVerbose bool
|
||||||
|
mergeReportPath string // Path to write merge report
|
||||||
)
|
)
|
||||||
|
|
||||||
var mergeCmd = &cobra.Command{
|
var mergeCmd = &cobra.Command{
|
||||||
@@ -78,6 +79,12 @@ Examples:
|
|||||||
--source pgsql --source-conn "postgres://user:pass@localhost/source_db" \
|
--source pgsql --source-conn "postgres://user:pass@localhost/source_db" \
|
||||||
--output json --output-path combined.json
|
--output json --output-path combined.json
|
||||||
|
|
||||||
|
# Merge and execute on PostgreSQL database with report
|
||||||
|
relspec merge --target json --target-path base.json \
|
||||||
|
--source json --source-path additional.json \
|
||||||
|
--output pgsql --output-conn "postgres://user:pass@localhost/target_db" \
|
||||||
|
--merge-report merge-report.json
|
||||||
|
|
||||||
# Merge DBML and YAML, skip relations
|
# Merge DBML and YAML, skip relations
|
||||||
relspec merge --target dbml --target-path schema.dbml \
|
relspec merge --target dbml --target-path schema.dbml \
|
||||||
--source yaml --source-path tables.yaml \
|
--source yaml --source-path tables.yaml \
|
||||||
@@ -115,6 +122,7 @@ func init() {
|
|||||||
mergeCmd.Flags().BoolVar(&mergeSkipSequences, "skip-sequences", false, "Skip sequences during merge")
|
mergeCmd.Flags().BoolVar(&mergeSkipSequences, "skip-sequences", false, "Skip sequences during merge")
|
||||||
mergeCmd.Flags().StringVar(&mergeSkipTables, "skip-tables", "", "Comma-separated list of table names to skip during merge")
|
mergeCmd.Flags().StringVar(&mergeSkipTables, "skip-tables", "", "Comma-separated list of table names to skip during merge")
|
||||||
mergeCmd.Flags().BoolVar(&mergeVerbose, "verbose", false, "Show verbose output")
|
mergeCmd.Flags().BoolVar(&mergeVerbose, "verbose", false, "Show verbose output")
|
||||||
|
mergeCmd.Flags().StringVar(&mergeReportPath, "merge-report", "", "Path to write merge report (JSON format)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMerge(cmd *cobra.Command, args []string) error {
|
func runMerge(cmd *cobra.Command, args []string) error {
|
||||||
@@ -229,7 +237,7 @@ func runMerge(cmd *cobra.Command, args []string) error {
|
|||||||
fmt.Fprintf(os.Stderr, " Path: %s\n", mergeOutputPath)
|
fmt.Fprintf(os.Stderr, " Path: %s\n", mergeOutputPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writeDatabaseForMerge(mergeOutputType, mergeOutputPath, "", targetDB, "Output")
|
err = writeDatabaseForMerge(mergeOutputType, mergeOutputPath, mergeOutputConn, targetDB, "Output")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write output: %w", err)
|
return fmt.Errorf("failed to write output: %w", err)
|
||||||
}
|
}
|
||||||
@@ -376,7 +384,17 @@ func writeDatabaseForMerge(dbType, filePath, connString string, db *models.Datab
|
|||||||
}
|
}
|
||||||
writer = wtypeorm.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wtypeorm.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
||||||
case "pgsql":
|
case "pgsql":
|
||||||
writer = wpgsql.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writerOpts := &writers.WriterOptions{OutputPath: filePath}
|
||||||
|
if connString != "" {
|
||||||
|
writerOpts.Metadata = map[string]interface{}{
|
||||||
|
"connection_string": connString,
|
||||||
|
}
|
||||||
|
// Add report path if merge report is enabled
|
||||||
|
if mergeReportPath != "" {
|
||||||
|
writerOpts.Metadata["report_path"] = mergeReportPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer = wpgsql.NewWriter(writerOpts)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%s: unsupported format '%s'", label, dbType)
|
return fmt.Errorf("%s: unsupported format '%s'", label, dbType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -587,10 +587,10 @@ func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column
|
|||||||
refOp := strings.TrimSpace(refStr)
|
refOp := strings.TrimSpace(refStr)
|
||||||
var isReverse bool
|
var isReverse bool
|
||||||
if strings.HasPrefix(refOp, "<") {
|
if strings.HasPrefix(refOp, "<") {
|
||||||
isReverse = column.IsPrimaryKey // < on PK means "is referenced by" (reverse)
|
// < means "is referenced by" - only makes sense on PK columns
|
||||||
} else if strings.HasPrefix(refOp, ">") {
|
isReverse = column.IsPrimaryKey
|
||||||
isReverse = !column.IsPrimaryKey // > on FK means reverse
|
|
||||||
}
|
}
|
||||||
|
// > means "references" - always a forward FK, never reverse
|
||||||
|
|
||||||
constraint = r.parseRef(refStr)
|
constraint = r.parseRef(refStr)
|
||||||
if constraint != nil {
|
if constraint != nil {
|
||||||
|
|||||||
@@ -329,10 +329,10 @@ func (r *Reader) deriveRelationship(table *models.Table, fk *models.Constraint)
|
|||||||
relationshipName := fmt.Sprintf("%s_to_%s", table.Name, fk.ReferencedTable)
|
relationshipName := fmt.Sprintf("%s_to_%s", table.Name, fk.ReferencedTable)
|
||||||
|
|
||||||
relationship := models.InitRelationship(relationshipName, models.OneToMany)
|
relationship := models.InitRelationship(relationshipName, models.OneToMany)
|
||||||
relationship.FromTable = fk.ReferencedTable
|
relationship.FromTable = table.Name
|
||||||
relationship.FromSchema = fk.ReferencedSchema
|
relationship.FromSchema = table.Schema
|
||||||
relationship.ToTable = table.Name
|
relationship.ToTable = fk.ReferencedTable
|
||||||
relationship.ToSchema = table.Schema
|
relationship.ToSchema = fk.ReferencedSchema
|
||||||
relationship.ForeignKey = fk.Name
|
relationship.ForeignKey = fk.Name
|
||||||
|
|
||||||
// Store constraint actions in properties
|
// Store constraint actions in properties
|
||||||
|
|||||||
@@ -328,12 +328,12 @@ func TestDeriveRelationship(t *testing.T) {
|
|||||||
t.Errorf("Expected relationship type %s, got %s", models.OneToMany, rel.Type)
|
t.Errorf("Expected relationship type %s, got %s", models.OneToMany, rel.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rel.FromTable != "users" {
|
if rel.FromTable != "orders" {
|
||||||
t.Errorf("Expected FromTable 'users', got '%s'", rel.FromTable)
|
t.Errorf("Expected FromTable 'orders', got '%s'", rel.FromTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rel.ToTable != "orders" {
|
if rel.ToTable != "users" {
|
||||||
t.Errorf("Expected ToTable 'orders', got '%s'", rel.ToTable)
|
t.Errorf("Expected ToTable 'users', got '%s'", rel.ToTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rel.ForeignKey != "fk_orders_user_id" {
|
if rel.ForeignKey != "fk_orders_user_id" {
|
||||||
|
|||||||
@@ -112,13 +112,17 @@ func NewModelData(table *models.Table, schema string, typeMapper *TypeMapper) *M
|
|||||||
tableName = schema + "." + table.Name
|
tableName = schema + "." + table.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate model name: singularize and convert to PascalCase
|
// Generate model name: Model + Schema + Table (all PascalCase)
|
||||||
singularTable := Singularize(table.Name)
|
singularTable := Singularize(table.Name)
|
||||||
modelName := SnakeCaseToPascalCase(singularTable)
|
tablePart := SnakeCaseToPascalCase(singularTable)
|
||||||
|
|
||||||
// Add "Model" prefix if not already present
|
// Include schema name in model name
|
||||||
if !hasModelPrefix(modelName) {
|
var modelName string
|
||||||
modelName = "Model" + modelName
|
if schema != "" {
|
||||||
|
schemaPart := SnakeCaseToPascalCase(schema)
|
||||||
|
modelName = "Model" + schemaPart + tablePart
|
||||||
|
} else {
|
||||||
|
modelName = "Model" + tablePart
|
||||||
}
|
}
|
||||||
|
|
||||||
model := &ModelData{
|
model := &ModelData{
|
||||||
@@ -192,11 +196,6 @@ func formatComment(description, comment string) string {
|
|||||||
return comment
|
return comment
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasModelPrefix checks if a name already has "Model" prefix
|
|
||||||
func hasModelPrefix(name string) bool {
|
|
||||||
return len(name) >= 5 && name[:5] == "Model"
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveFieldNameCollision checks if a field name conflicts with generated method names
|
// resolveFieldNameCollision checks if a field name conflicts with generated method names
|
||||||
// and adds an underscore suffix if there's a collision
|
// and adds an underscore suffix if there's a collision
|
||||||
func resolveFieldNameCollision(fieldName string) string {
|
func resolveFieldNameCollision(fieldName string) string {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/format"
|
"go/format"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -124,7 +125,16 @@ func (w *Writer) writeSingleFile(db *models.Database) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write output
|
// Write output
|
||||||
return w.writeOutput(formatted)
|
if err := w.writeOutput(formatted); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run go fmt on the output file
|
||||||
|
if w.options.OutputPath != "" {
|
||||||
|
w.runGoFmt(w.options.OutputPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeMultiFile writes each table to a separate file
|
// writeMultiFile writes each table to a separate file
|
||||||
@@ -217,6 +227,9 @@ func (w *Writer) writeMultiFile(db *models.Database) error {
|
|||||||
if err := os.WriteFile(filepath, []byte(formatted), 0644); err != nil {
|
if err := os.WriteFile(filepath, []byte(formatted), 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write file %s: %w", filename, err)
|
return fmt.Errorf("failed to write file %s: %w", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run go fmt on the generated file
|
||||||
|
w.runGoFmt(filepath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +254,7 @@ func (w *Writer) addRelationshipFields(modelData *ModelData, table *models.Table
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create relationship field (has-one in Bun, similar to belongs-to in GORM)
|
// Create relationship field (has-one in Bun, similar to belongs-to in GORM)
|
||||||
refModelName := w.getModelName(constraint.ReferencedTable)
|
refModelName := w.getModelName(constraint.ReferencedSchema, constraint.ReferencedTable)
|
||||||
fieldName := w.generateHasOneFieldName(constraint)
|
fieldName := w.generateHasOneFieldName(constraint)
|
||||||
fieldName = w.ensureUniqueFieldName(fieldName, usedFieldNames)
|
fieldName = w.ensureUniqueFieldName(fieldName, usedFieldNames)
|
||||||
relationTag := w.typeMapper.BuildRelationshipTag(constraint, "has-one")
|
relationTag := w.typeMapper.BuildRelationshipTag(constraint, "has-one")
|
||||||
@@ -270,8 +283,8 @@ func (w *Writer) addRelationshipFields(modelData *ModelData, table *models.Table
|
|||||||
// Check if this constraint references our table
|
// Check if this constraint references our table
|
||||||
if constraint.ReferencedTable == table.Name && constraint.ReferencedSchema == schema.Name {
|
if constraint.ReferencedTable == table.Name && constraint.ReferencedSchema == schema.Name {
|
||||||
// Add has-many relationship
|
// Add has-many relationship
|
||||||
otherModelName := w.getModelName(otherTable.Name)
|
otherModelName := w.getModelName(otherSchema.Name, otherTable.Name)
|
||||||
fieldName := w.generateHasManyFieldName(constraint, otherTable.Name)
|
fieldName := w.generateHasManyFieldName(constraint, otherSchema.Name, otherTable.Name)
|
||||||
fieldName = w.ensureUniqueFieldName(fieldName, usedFieldNames)
|
fieldName = w.ensureUniqueFieldName(fieldName, usedFieldNames)
|
||||||
relationTag := w.typeMapper.BuildRelationshipTag(constraint, "has-many")
|
relationTag := w.typeMapper.BuildRelationshipTag(constraint, "has-many")
|
||||||
|
|
||||||
@@ -303,13 +316,18 @@ func (w *Writer) findTable(schemaName, tableName string, db *models.Database) *m
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getModelName generates the model name from a table name
|
// getModelName generates the model name from schema and table name
|
||||||
func (w *Writer) getModelName(tableName string) string {
|
func (w *Writer) getModelName(schemaName, tableName string) string {
|
||||||
singular := Singularize(tableName)
|
singular := Singularize(tableName)
|
||||||
modelName := SnakeCaseToPascalCase(singular)
|
tablePart := SnakeCaseToPascalCase(singular)
|
||||||
|
|
||||||
if !hasModelPrefix(modelName) {
|
// Include schema name in model name
|
||||||
modelName = "Model" + modelName
|
var modelName string
|
||||||
|
if schemaName != "" {
|
||||||
|
schemaPart := SnakeCaseToPascalCase(schemaName)
|
||||||
|
modelName = "Model" + schemaPart + tablePart
|
||||||
|
} else {
|
||||||
|
modelName = "Model" + tablePart
|
||||||
}
|
}
|
||||||
|
|
||||||
return modelName
|
return modelName
|
||||||
@@ -333,13 +351,13 @@ func (w *Writer) generateHasOneFieldName(constraint *models.Constraint) string {
|
|||||||
|
|
||||||
// generateHasManyFieldName generates a field name for has-many relationships
|
// generateHasManyFieldName generates a field name for has-many relationships
|
||||||
// Uses the foreign key column name + source table name to avoid duplicates
|
// Uses the foreign key column name + source table name to avoid duplicates
|
||||||
func (w *Writer) generateHasManyFieldName(constraint *models.Constraint, sourceTableName string) string {
|
func (w *Writer) generateHasManyFieldName(constraint *models.Constraint, sourceSchemaName, sourceTableName string) string {
|
||||||
// For has-many, we need to include the source table name to avoid duplicates
|
// For has-many, we need to include the source table name to avoid duplicates
|
||||||
// e.g., multiple tables referencing the same column on this table
|
// e.g., multiple tables referencing the same column on this table
|
||||||
if len(constraint.Columns) > 0 {
|
if len(constraint.Columns) > 0 {
|
||||||
columnName := constraint.Columns[0]
|
columnName := constraint.Columns[0]
|
||||||
// Get the model name for the source table (pluralized)
|
// Get the model name for the source table (pluralized)
|
||||||
sourceModelName := w.getModelName(sourceTableName)
|
sourceModelName := w.getModelName(sourceSchemaName, sourceTableName)
|
||||||
// Remove "Model" prefix if present
|
// Remove "Model" prefix if present
|
||||||
sourceModelName = strings.TrimPrefix(sourceModelName, "Model")
|
sourceModelName = strings.TrimPrefix(sourceModelName, "Model")
|
||||||
|
|
||||||
@@ -350,7 +368,7 @@ func (w *Writer) generateHasManyFieldName(constraint *models.Constraint, sourceT
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to table-based naming
|
// Fallback to table-based naming
|
||||||
sourceModelName := w.getModelName(sourceTableName)
|
sourceModelName := w.getModelName(sourceSchemaName, sourceTableName)
|
||||||
sourceModelName = strings.TrimPrefix(sourceModelName, "Model")
|
sourceModelName = strings.TrimPrefix(sourceModelName, "Model")
|
||||||
return "Rel" + Pluralize(sourceModelName)
|
return "Rel" + Pluralize(sourceModelName)
|
||||||
}
|
}
|
||||||
@@ -399,6 +417,15 @@ func (w *Writer) writeOutput(content string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runGoFmt runs go fmt on the specified file
|
||||||
|
func (w *Writer) runGoFmt(filepath string) {
|
||||||
|
cmd := exec.Command("gofmt", "-w", filepath)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
// Don't fail the whole operation if gofmt fails, just warn
|
||||||
|
fmt.Fprintf(os.Stderr, "Warning: failed to run gofmt on %s: %v\n", filepath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// shouldUseMultiFile determines whether to use multi-file mode based on metadata or output path
|
// shouldUseMultiFile determines whether to use multi-file mode based on metadata or output path
|
||||||
func (w *Writer) shouldUseMultiFile() bool {
|
func (w *Writer) shouldUseMultiFile() bool {
|
||||||
// Check if multi_file is explicitly set in metadata
|
// Check if multi_file is explicitly set in metadata
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func TestWriter_WriteTable(t *testing.T) {
|
|||||||
// Verify key elements are present
|
// Verify key elements are present
|
||||||
expectations := []string{
|
expectations := []string{
|
||||||
"package models",
|
"package models",
|
||||||
"type ModelUser struct",
|
"type ModelPublicUser struct",
|
||||||
"bun.BaseModel",
|
"bun.BaseModel",
|
||||||
"table:public.users",
|
"table:public.users",
|
||||||
"alias:users",
|
"alias:users",
|
||||||
@@ -78,9 +78,9 @@ func TestWriter_WriteTable(t *testing.T) {
|
|||||||
"resolvespec_common.SqlTime",
|
"resolvespec_common.SqlTime",
|
||||||
"bun:\"id",
|
"bun:\"id",
|
||||||
"bun:\"email",
|
"bun:\"email",
|
||||||
"func (m ModelUser) TableName() string",
|
"func (m ModelPublicUser) TableName() string",
|
||||||
"return \"public.users\"",
|
"return \"public.users\"",
|
||||||
"func (m ModelUser) GetID() int64",
|
"func (m ModelPublicUser) GetID() int64",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expected := range expectations {
|
for _, expected := range expectations {
|
||||||
@@ -191,9 +191,9 @@ func TestWriter_WriteDatabase_MultiFile(t *testing.T) {
|
|||||||
|
|
||||||
usersStr := string(usersContent)
|
usersStr := string(usersContent)
|
||||||
|
|
||||||
// Should have RelUserIDPosts (has-many) field
|
// Should have RelUserIDPublicPosts (has-many) field - includes schema prefix
|
||||||
if !strings.Contains(usersStr, "RelUserIDPosts") {
|
if !strings.Contains(usersStr, "RelUserIDPublicPosts") {
|
||||||
t.Errorf("Missing has-many relationship field RelUserIDPosts")
|
t.Errorf("Missing has-many relationship field RelUserIDPublicPosts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,8 +309,8 @@ func TestWriter_MultipleReferencesToSameTable(t *testing.T) {
|
|||||||
|
|
||||||
// Should have two different has-many relationships with unique names
|
// Should have two different has-many relationships with unique names
|
||||||
hasManyExpectations := []string{
|
hasManyExpectations := []string{
|
||||||
"RelRIDFilepointerRequestAPIEvents", // Has many via rid_filepointer_request
|
"RelRIDFilepointerRequestOrgAPIEvents", // Has many via rid_filepointer_request
|
||||||
"RelRIDFilepointerResponseAPIEvents", // Has many via rid_filepointer_response
|
"RelRIDFilepointerResponseOrgAPIEvents", // Has many via rid_filepointer_response
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, exp := range hasManyExpectations {
|
for _, exp := range hasManyExpectations {
|
||||||
@@ -455,9 +455,9 @@ func TestWriter_MultipleHasManyRelationships(t *testing.T) {
|
|||||||
|
|
||||||
// Verify all has-many relationships have unique names
|
// Verify all has-many relationships have unique names
|
||||||
hasManyExpectations := []string{
|
hasManyExpectations := []string{
|
||||||
"RelRIDAPIProviderLogins", // Has many via Login
|
"RelRIDAPIProviderOrgLogins", // Has many via Login
|
||||||
"RelRIDAPIProviderFilepointers", // Has many via Filepointer
|
"RelRIDAPIProviderOrgFilepointers", // Has many via Filepointer
|
||||||
"RelRIDAPIProviderAPIEvents", // Has many via APIEvent
|
"RelRIDAPIProviderOrgAPIEvents", // Has many via APIEvent
|
||||||
"RelRIDOwner", // Has one via rid_owner
|
"RelRIDOwner", // Has one via rid_owner
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,7 +539,7 @@ func TestWriter_FieldNameCollision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify the TableName() method still exists and doesn't conflict
|
// Verify the TableName() method still exists and doesn't conflict
|
||||||
if !strings.Contains(generated, "func (m ModelAuditTable) TableName() string") {
|
if !strings.Contains(generated, "func (m ModelAuditAuditTable) TableName() string") {
|
||||||
t.Errorf("TableName() method should still be generated\nGenerated:\n%s", generated)
|
t.Errorf("TableName() method should still be generated\nGenerated:\n%s", generated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,13 +111,17 @@ func NewModelData(table *models.Table, schema string, typeMapper *TypeMapper) *M
|
|||||||
tableName = schema + "." + table.Name
|
tableName = schema + "." + table.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate model name: singularize and convert to PascalCase
|
// Generate model name: Model + Schema + Table (all PascalCase)
|
||||||
singularTable := Singularize(table.Name)
|
singularTable := Singularize(table.Name)
|
||||||
modelName := SnakeCaseToPascalCase(singularTable)
|
tablePart := SnakeCaseToPascalCase(singularTable)
|
||||||
|
|
||||||
// Add "Model" prefix if not already present
|
// Include schema name in model name
|
||||||
if !hasModelPrefix(modelName) {
|
var modelName string
|
||||||
modelName = "Model" + modelName
|
if schema != "" {
|
||||||
|
schemaPart := SnakeCaseToPascalCase(schema)
|
||||||
|
modelName = "Model" + schemaPart + tablePart
|
||||||
|
} else {
|
||||||
|
modelName = "Model" + tablePart
|
||||||
}
|
}
|
||||||
|
|
||||||
model := &ModelData{
|
model := &ModelData{
|
||||||
@@ -189,11 +193,6 @@ func formatComment(description, comment string) string {
|
|||||||
return comment
|
return comment
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasModelPrefix checks if a name already has "Model" prefix
|
|
||||||
func hasModelPrefix(name string) bool {
|
|
||||||
return len(name) >= 5 && name[:5] == "Model"
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveFieldNameCollision checks if a field name conflicts with generated method names
|
// resolveFieldNameCollision checks if a field name conflicts with generated method names
|
||||||
// and adds an underscore suffix if there's a collision
|
// and adds an underscore suffix if there's a collision
|
||||||
func resolveFieldNameCollision(fieldName string) string {
|
func resolveFieldNameCollision(fieldName string) string {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/format"
|
"go/format"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -121,7 +122,16 @@ func (w *Writer) writeSingleFile(db *models.Database) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write output
|
// Write output
|
||||||
return w.writeOutput(formatted)
|
if err := w.writeOutput(formatted); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run go fmt on the output file
|
||||||
|
if w.options.OutputPath != "" {
|
||||||
|
w.runGoFmt(w.options.OutputPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeMultiFile writes each table to a separate file
|
// writeMultiFile writes each table to a separate file
|
||||||
@@ -211,6 +221,9 @@ func (w *Writer) writeMultiFile(db *models.Database) error {
|
|||||||
if err := os.WriteFile(filepath, []byte(formatted), 0644); err != nil {
|
if err := os.WriteFile(filepath, []byte(formatted), 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write file %s: %w", filename, err)
|
return fmt.Errorf("failed to write file %s: %w", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run go fmt on the generated file
|
||||||
|
w.runGoFmt(filepath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +248,7 @@ func (w *Writer) addRelationshipFields(modelData *ModelData, table *models.Table
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create relationship field (belongs-to)
|
// Create relationship field (belongs-to)
|
||||||
refModelName := w.getModelName(constraint.ReferencedTable)
|
refModelName := w.getModelName(constraint.ReferencedSchema, constraint.ReferencedTable)
|
||||||
fieldName := w.generateBelongsToFieldName(constraint)
|
fieldName := w.generateBelongsToFieldName(constraint)
|
||||||
fieldName = w.ensureUniqueFieldName(fieldName, usedFieldNames)
|
fieldName = w.ensureUniqueFieldName(fieldName, usedFieldNames)
|
||||||
relationTag := w.typeMapper.BuildRelationshipTag(constraint, false)
|
relationTag := w.typeMapper.BuildRelationshipTag(constraint, false)
|
||||||
@@ -264,8 +277,8 @@ func (w *Writer) addRelationshipFields(modelData *ModelData, table *models.Table
|
|||||||
// Check if this constraint references our table
|
// Check if this constraint references our table
|
||||||
if constraint.ReferencedTable == table.Name && constraint.ReferencedSchema == schema.Name {
|
if constraint.ReferencedTable == table.Name && constraint.ReferencedSchema == schema.Name {
|
||||||
// Add has-many relationship
|
// Add has-many relationship
|
||||||
otherModelName := w.getModelName(otherTable.Name)
|
otherModelName := w.getModelName(otherSchema.Name, otherTable.Name)
|
||||||
fieldName := w.generateHasManyFieldName(constraint, otherTable.Name)
|
fieldName := w.generateHasManyFieldName(constraint, otherSchema.Name, otherTable.Name)
|
||||||
fieldName = w.ensureUniqueFieldName(fieldName, usedFieldNames)
|
fieldName = w.ensureUniqueFieldName(fieldName, usedFieldNames)
|
||||||
relationTag := w.typeMapper.BuildRelationshipTag(constraint, true)
|
relationTag := w.typeMapper.BuildRelationshipTag(constraint, true)
|
||||||
|
|
||||||
@@ -297,13 +310,18 @@ func (w *Writer) findTable(schemaName, tableName string, db *models.Database) *m
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getModelName generates the model name from a table name
|
// getModelName generates the model name from schema and table name
|
||||||
func (w *Writer) getModelName(tableName string) string {
|
func (w *Writer) getModelName(schemaName, tableName string) string {
|
||||||
singular := Singularize(tableName)
|
singular := Singularize(tableName)
|
||||||
modelName := SnakeCaseToPascalCase(singular)
|
tablePart := SnakeCaseToPascalCase(singular)
|
||||||
|
|
||||||
if !hasModelPrefix(modelName) {
|
// Include schema name in model name
|
||||||
modelName = "Model" + modelName
|
var modelName string
|
||||||
|
if schemaName != "" {
|
||||||
|
schemaPart := SnakeCaseToPascalCase(schemaName)
|
||||||
|
modelName = "Model" + schemaPart + tablePart
|
||||||
|
} else {
|
||||||
|
modelName = "Model" + tablePart
|
||||||
}
|
}
|
||||||
|
|
||||||
return modelName
|
return modelName
|
||||||
@@ -327,13 +345,13 @@ func (w *Writer) generateBelongsToFieldName(constraint *models.Constraint) strin
|
|||||||
|
|
||||||
// generateHasManyFieldName generates a field name for has-many relationships
|
// generateHasManyFieldName generates a field name for has-many relationships
|
||||||
// Uses the foreign key column name + source table name to avoid duplicates
|
// Uses the foreign key column name + source table name to avoid duplicates
|
||||||
func (w *Writer) generateHasManyFieldName(constraint *models.Constraint, sourceTableName string) string {
|
func (w *Writer) generateHasManyFieldName(constraint *models.Constraint, sourceSchemaName, sourceTableName string) string {
|
||||||
// For has-many, we need to include the source table name to avoid duplicates
|
// For has-many, we need to include the source table name to avoid duplicates
|
||||||
// e.g., multiple tables referencing the same column on this table
|
// e.g., multiple tables referencing the same column on this table
|
||||||
if len(constraint.Columns) > 0 {
|
if len(constraint.Columns) > 0 {
|
||||||
columnName := constraint.Columns[0]
|
columnName := constraint.Columns[0]
|
||||||
// Get the model name for the source table (pluralized)
|
// Get the model name for the source table (pluralized)
|
||||||
sourceModelName := w.getModelName(sourceTableName)
|
sourceModelName := w.getModelName(sourceSchemaName, sourceTableName)
|
||||||
// Remove "Model" prefix if present
|
// Remove "Model" prefix if present
|
||||||
sourceModelName = strings.TrimPrefix(sourceModelName, "Model")
|
sourceModelName = strings.TrimPrefix(sourceModelName, "Model")
|
||||||
|
|
||||||
@@ -344,7 +362,7 @@ func (w *Writer) generateHasManyFieldName(constraint *models.Constraint, sourceT
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to table-based naming
|
// Fallback to table-based naming
|
||||||
sourceModelName := w.getModelName(sourceTableName)
|
sourceModelName := w.getModelName(sourceSchemaName, sourceTableName)
|
||||||
sourceModelName = strings.TrimPrefix(sourceModelName, "Model")
|
sourceModelName = strings.TrimPrefix(sourceModelName, "Model")
|
||||||
return "Rel" + Pluralize(sourceModelName)
|
return "Rel" + Pluralize(sourceModelName)
|
||||||
}
|
}
|
||||||
@@ -393,6 +411,15 @@ func (w *Writer) writeOutput(content string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runGoFmt runs go fmt on the specified file
|
||||||
|
func (w *Writer) runGoFmt(filepath string) {
|
||||||
|
cmd := exec.Command("gofmt", "-w", filepath)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
// Don't fail the whole operation if gofmt fails, just warn
|
||||||
|
fmt.Fprintf(os.Stderr, "Warning: failed to run gofmt on %s: %v\n", filepath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// shouldUseMultiFile determines whether to use multi-file mode based on metadata or output path
|
// shouldUseMultiFile determines whether to use multi-file mode based on metadata or output path
|
||||||
func (w *Writer) shouldUseMultiFile() bool {
|
func (w *Writer) shouldUseMultiFile() bool {
|
||||||
// Check if multi_file is explicitly set in metadata
|
// Check if multi_file is explicitly set in metadata
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func TestWriter_WriteTable(t *testing.T) {
|
|||||||
// Verify key elements are present
|
// Verify key elements are present
|
||||||
expectations := []string{
|
expectations := []string{
|
||||||
"package models",
|
"package models",
|
||||||
"type ModelUser struct",
|
"type ModelPublicUser struct",
|
||||||
"ID",
|
"ID",
|
||||||
"int64",
|
"int64",
|
||||||
"Email",
|
"Email",
|
||||||
@@ -75,9 +75,9 @@ func TestWriter_WriteTable(t *testing.T) {
|
|||||||
"time.Time",
|
"time.Time",
|
||||||
"gorm:\"column:id",
|
"gorm:\"column:id",
|
||||||
"gorm:\"column:email",
|
"gorm:\"column:email",
|
||||||
"func (m ModelUser) TableName() string",
|
"func (m ModelPublicUser) TableName() string",
|
||||||
"return \"public.users\"",
|
"return \"public.users\"",
|
||||||
"func (m ModelUser) GetID() int64",
|
"func (m ModelPublicUser) GetID() int64",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expected := range expectations {
|
for _, expected := range expectations {
|
||||||
@@ -180,9 +180,9 @@ func TestWriter_WriteDatabase_MultiFile(t *testing.T) {
|
|||||||
|
|
||||||
usersStr := string(usersContent)
|
usersStr := string(usersContent)
|
||||||
|
|
||||||
// Should have RelUserIDPosts (has-many) field
|
// Should have RelUserIDPublicPosts (has-many) field - includes schema prefix
|
||||||
if !strings.Contains(usersStr, "RelUserIDPosts") {
|
if !strings.Contains(usersStr, "RelUserIDPublicPosts") {
|
||||||
t.Errorf("Missing has-many relationship field RelUserIDPosts")
|
t.Errorf("Missing has-many relationship field RelUserIDPublicPosts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,8 +298,8 @@ func TestWriter_MultipleReferencesToSameTable(t *testing.T) {
|
|||||||
|
|
||||||
// Should have two different has-many relationships with unique names
|
// Should have two different has-many relationships with unique names
|
||||||
hasManyExpectations := []string{
|
hasManyExpectations := []string{
|
||||||
"RelRIDFilepointerRequestAPIEvents", // Has many via rid_filepointer_request
|
"RelRIDFilepointerRequestOrgAPIEvents", // Has many via rid_filepointer_request
|
||||||
"RelRIDFilepointerResponseAPIEvents", // Has many via rid_filepointer_response
|
"RelRIDFilepointerResponseOrgAPIEvents", // Has many via rid_filepointer_response
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, exp := range hasManyExpectations {
|
for _, exp := range hasManyExpectations {
|
||||||
@@ -444,9 +444,9 @@ func TestWriter_MultipleHasManyRelationships(t *testing.T) {
|
|||||||
|
|
||||||
// Verify all has-many relationships have unique names
|
// Verify all has-many relationships have unique names
|
||||||
hasManyExpectations := []string{
|
hasManyExpectations := []string{
|
||||||
"RelRIDAPIProviderLogins", // Has many via Login
|
"RelRIDAPIProviderOrgLogins", // Has many via Login
|
||||||
"RelRIDAPIProviderFilepointers", // Has many via Filepointer
|
"RelRIDAPIProviderOrgFilepointers", // Has many via Filepointer
|
||||||
"RelRIDAPIProviderAPIEvents", // Has many via APIEvent
|
"RelRIDAPIProviderOrgAPIEvents", // Has many via APIEvent
|
||||||
"RelRIDOwner", // Belongs to via rid_owner
|
"RelRIDOwner", // Belongs to via rid_owner
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +528,7 @@ func TestWriter_FieldNameCollision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify the TableName() method still exists and doesn't conflict
|
// Verify the TableName() method still exists and doesn't conflict
|
||||||
if !strings.Contains(generated, "func (m ModelAuditTable) TableName() string") {
|
if !strings.Contains(generated, "func (m ModelAuditAuditTable) TableName() string") {
|
||||||
t.Errorf("TableName() method should still be generated\nGenerated:\n%s", generated)
|
t.Errorf("TableName() method should still be generated\nGenerated:\n%s", generated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -427,9 +427,11 @@ func (w *MigrationWriter) generateIndexScripts(model *models.Schema, current *mo
|
|||||||
for _, modelTable := range model.Tables {
|
for _, modelTable := range model.Tables {
|
||||||
currentTable := currentTables[strings.ToLower(modelTable.Name)]
|
currentTable := currentTables[strings.ToLower(modelTable.Name)]
|
||||||
|
|
||||||
// Process primary keys first
|
// Process primary keys first - check explicit constraints
|
||||||
|
foundExplicitPK := false
|
||||||
for constraintName, constraint := range modelTable.Constraints {
|
for constraintName, constraint := range modelTable.Constraints {
|
||||||
if constraint.Type == models.PrimaryKeyConstraint {
|
if constraint.Type == models.PrimaryKeyConstraint {
|
||||||
|
foundExplicitPK = true
|
||||||
shouldCreate := true
|
shouldCreate := true
|
||||||
|
|
||||||
if currentTable != nil {
|
if currentTable != nil {
|
||||||
@@ -464,6 +466,53 @@ func (w *MigrationWriter) generateIndexScripts(model *models.Schema, current *mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no explicit PK constraint, check for columns with IsPrimaryKey = true
|
||||||
|
if !foundExplicitPK {
|
||||||
|
pkColumns := []string{}
|
||||||
|
for _, col := range modelTable.Columns {
|
||||||
|
if col.IsPrimaryKey {
|
||||||
|
pkColumns = append(pkColumns, col.SQLName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(pkColumns) > 0 {
|
||||||
|
sort.Strings(pkColumns)
|
||||||
|
constraintName := fmt.Sprintf("pk_%s_%s", strings.ToLower(model.Name), strings.ToLower(modelTable.Name))
|
||||||
|
shouldCreate := true
|
||||||
|
|
||||||
|
if currentTable != nil {
|
||||||
|
// Check if a PK constraint already exists (by any name)
|
||||||
|
for _, constraint := range currentTable.Constraints {
|
||||||
|
if constraint.Type == models.PrimaryKeyConstraint {
|
||||||
|
shouldCreate = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldCreate {
|
||||||
|
sql, err := w.executor.ExecuteCreatePrimaryKey(CreatePrimaryKeyData{
|
||||||
|
SchemaName: model.Name,
|
||||||
|
TableName: modelTable.Name,
|
||||||
|
ConstraintName: constraintName,
|
||||||
|
Columns: strings.Join(pkColumns, ", "),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
script := MigrationScript{
|
||||||
|
ObjectName: fmt.Sprintf("%s.%s.%s", model.Name, modelTable.Name, constraintName),
|
||||||
|
ObjectType: "create primary key",
|
||||||
|
Schema: model.Name,
|
||||||
|
Priority: 160,
|
||||||
|
Sequence: len(scripts),
|
||||||
|
Body: sql,
|
||||||
|
}
|
||||||
|
scripts = append(scripts, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Process indexes
|
// Process indexes
|
||||||
for indexName, modelIndex := range modelTable.Indexes {
|
for indexName, modelIndex := range modelTable.Indexes {
|
||||||
// Skip primary key indexes
|
// Skip primary key indexes
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package pgsql
|
package pgsql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
|
||||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||||
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||||||
@@ -15,6 +20,38 @@ import (
|
|||||||
type Writer struct {
|
type Writer struct {
|
||||||
options *writers.WriterOptions
|
options *writers.WriterOptions
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
|
executionReport *ExecutionReport
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecutionReport tracks the execution status of SQL statements
|
||||||
|
type ExecutionReport struct {
|
||||||
|
TotalStatements int `json:"total_statements"`
|
||||||
|
ExecutedStatements int `json:"executed_statements"`
|
||||||
|
FailedStatements int `json:"failed_statements"`
|
||||||
|
Schemas []SchemaReport `json:"schemas"`
|
||||||
|
Errors []ExecutionError `json:"errors,omitempty"`
|
||||||
|
StartTime string `json:"start_time"`
|
||||||
|
EndTime string `json:"end_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchemaReport tracks execution per schema
|
||||||
|
type SchemaReport struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tables []TableReport `json:"tables"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableReport tracks execution per table
|
||||||
|
type TableReport struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Created bool `json:"created"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecutionError represents a failed statement
|
||||||
|
type ExecutionError struct {
|
||||||
|
StatementNumber int `json:"statement_number"`
|
||||||
|
Statement string `json:"statement"`
|
||||||
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWriter creates a new PostgreSQL SQL writer
|
// NewWriter creates a new PostgreSQL SQL writer
|
||||||
@@ -26,6 +63,11 @@ func NewWriter(options *writers.WriterOptions) *Writer {
|
|||||||
|
|
||||||
// WriteDatabase writes the entire database schema as SQL
|
// WriteDatabase writes the entire database schema as SQL
|
||||||
func (w *Writer) WriteDatabase(db *models.Database) error {
|
func (w *Writer) WriteDatabase(db *models.Database) error {
|
||||||
|
// Check if we should execute SQL directly on a database
|
||||||
|
if connString, ok := w.options.Metadata["connection_string"].(string); ok && connString != "" {
|
||||||
|
return w.executeDatabaseSQL(db, connString)
|
||||||
|
}
|
||||||
|
|
||||||
var writer io.Writer
|
var writer io.Writer
|
||||||
var file *os.File
|
var file *os.File
|
||||||
var err error
|
var err error
|
||||||
@@ -127,13 +169,35 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
|||||||
|
|
||||||
// Phase 4: Primary keys
|
// Phase 4: Primary keys
|
||||||
for _, table := range schema.Tables {
|
for _, table := range schema.Tables {
|
||||||
|
// First check for explicit PrimaryKeyConstraint
|
||||||
|
var pkConstraint *models.Constraint
|
||||||
for _, constraint := range table.Constraints {
|
for _, constraint := range table.Constraints {
|
||||||
if constraint.Type != models.PrimaryKeyConstraint {
|
if constraint.Type == models.PrimaryKeyConstraint {
|
||||||
continue
|
pkConstraint = constraint
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkConstraint != nil {
|
||||||
stmt := fmt.Sprintf("ALTER TABLE %s.%s ADD CONSTRAINT %s PRIMARY KEY (%s)",
|
stmt := fmt.Sprintf("ALTER TABLE %s.%s ADD CONSTRAINT %s PRIMARY KEY (%s)",
|
||||||
schema.SQLName(), table.SQLName(), constraint.Name, strings.Join(constraint.Columns, ", "))
|
schema.SQLName(), table.SQLName(), pkConstraint.Name, strings.Join(pkConstraint.Columns, ", "))
|
||||||
statements = append(statements, stmt)
|
statements = append(statements, stmt)
|
||||||
|
} else {
|
||||||
|
// No explicit constraint, check for columns with IsPrimaryKey = true
|
||||||
|
pkColumns := []string{}
|
||||||
|
for _, col := range table.Columns {
|
||||||
|
if col.IsPrimaryKey {
|
||||||
|
pkColumns = append(pkColumns, col.SQLName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(pkColumns) > 0 {
|
||||||
|
// Sort for consistent output
|
||||||
|
sort.Strings(pkColumns)
|
||||||
|
pkName := fmt.Sprintf("pk_%s_%s", schema.SQLName(), table.SQLName())
|
||||||
|
stmt := fmt.Sprintf("ALTER TABLE %s.%s ADD CONSTRAINT %s PRIMARY KEY (%s)",
|
||||||
|
schema.SQLName(), table.SQLName(), pkName, strings.Join(pkColumns, ", "))
|
||||||
|
statements = append(statements, stmt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,13 +219,30 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
|||||||
indexType = "btree"
|
indexType = "btree"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build column expressions with operator class support for GIN indexes
|
||||||
|
columnExprs := make([]string, 0, len(index.Columns))
|
||||||
|
for _, colName := range index.Columns {
|
||||||
|
colExpr := colName
|
||||||
|
if col, ok := table.Columns[colName]; ok {
|
||||||
|
// For GIN indexes on text columns, add operator class
|
||||||
|
if strings.EqualFold(indexType, "gin") && isTextType(col.Type) {
|
||||||
|
opClass := extractOperatorClass(index.Comment)
|
||||||
|
if opClass == "" {
|
||||||
|
opClass = "gin_trgm_ops"
|
||||||
|
}
|
||||||
|
colExpr = fmt.Sprintf("%s %s", colName, opClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columnExprs = append(columnExprs, colExpr)
|
||||||
|
}
|
||||||
|
|
||||||
whereClause := ""
|
whereClause := ""
|
||||||
if index.Where != "" {
|
if index.Where != "" {
|
||||||
whereClause = fmt.Sprintf(" WHERE %s", index.Where)
|
whereClause = fmt.Sprintf(" WHERE %s", index.Where)
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt := fmt.Sprintf("CREATE %sINDEX IF NOT EXISTS %s ON %s.%s USING %s (%s)%s",
|
stmt := fmt.Sprintf("CREATE %sINDEX IF NOT EXISTS %s ON %s.%s USING %s (%s)%s",
|
||||||
uniqueStr, index.Name, schema.SQLName(), table.SQLName(), indexType, strings.Join(index.Columns, ", "), whereClause)
|
uniqueStr, index.Name, schema.SQLName(), table.SQLName(), indexType, strings.Join(columnExprs, ", "), whereClause)
|
||||||
statements = append(statements, stmt)
|
statements = append(statements, stmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,12 +354,14 @@ func (w *Writer) generateColumnDefinition(col *models.Column) string {
|
|||||||
if col.Default != nil {
|
if col.Default != nil {
|
||||||
switch v := col.Default.(type) {
|
switch v := col.Default.(type) {
|
||||||
case string:
|
case string:
|
||||||
if strings.HasPrefix(v, "nextval") || strings.HasPrefix(v, "CURRENT_") || strings.Contains(v, "()") {
|
// Strip backticks - DBML uses them for SQL expressions but PostgreSQL doesn't
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", v))
|
cleanDefault := stripBackticks(v)
|
||||||
} else if v == "true" || v == "false" {
|
if strings.HasPrefix(cleanDefault, "nextval") || strings.HasPrefix(cleanDefault, "CURRENT_") || strings.Contains(cleanDefault, "()") {
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", v))
|
parts = append(parts, fmt.Sprintf("DEFAULT %s", cleanDefault))
|
||||||
|
} else if cleanDefault == "true" || cleanDefault == "false" {
|
||||||
|
parts = append(parts, fmt.Sprintf("DEFAULT %s", cleanDefault))
|
||||||
} else {
|
} else {
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT '%s'", escapeQuote(v)))
|
parts = append(parts, fmt.Sprintf("DEFAULT '%s'", escapeQuote(cleanDefault)))
|
||||||
}
|
}
|
||||||
case bool:
|
case bool:
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %v", v))
|
parts = append(parts, fmt.Sprintf("DEFAULT %v", v))
|
||||||
@@ -408,8 +491,10 @@ func (w *Writer) writeCreateTables(schema *models.Schema) error {
|
|||||||
colDef := fmt.Sprintf(" %s %s", col.SQLName(), col.Type)
|
colDef := fmt.Sprintf(" %s %s", col.SQLName(), col.Type)
|
||||||
|
|
||||||
// Add default value if present
|
// Add default value if present
|
||||||
if col.Default != "" {
|
if col.Default != nil && col.Default != "" {
|
||||||
colDef += fmt.Sprintf(" DEFAULT %s", col.Default)
|
// Strip backticks - DBML uses them for SQL expressions but PostgreSQL doesn't
|
||||||
|
defaultVal := fmt.Sprintf("%v", col.Default)
|
||||||
|
colDef += fmt.Sprintf(" DEFAULT %s", stripBackticks(defaultVal))
|
||||||
}
|
}
|
||||||
|
|
||||||
columnDefs = append(columnDefs, colDef)
|
columnDefs = append(columnDefs, colDef)
|
||||||
@@ -437,20 +522,27 @@ func (w *Writer) writePrimaryKeys(schema *models.Schema) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkConstraint == nil {
|
var columnNames []string
|
||||||
// No explicit PK constraint, skip
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pkName := fmt.Sprintf("pk_%s_%s", schema.SQLName(), table.SQLName())
|
pkName := fmt.Sprintf("pk_%s_%s", schema.SQLName(), table.SQLName())
|
||||||
|
|
||||||
// Build column list
|
if pkConstraint != nil {
|
||||||
columnNames := make([]string, 0, len(pkConstraint.Columns))
|
// Build column list from explicit constraint
|
||||||
|
columnNames = make([]string, 0, len(pkConstraint.Columns))
|
||||||
for _, colName := range pkConstraint.Columns {
|
for _, colName := range pkConstraint.Columns {
|
||||||
if col, ok := table.Columns[colName]; ok {
|
if col, ok := table.Columns[colName]; ok {
|
||||||
columnNames = append(columnNames, col.SQLName())
|
columnNames = append(columnNames, col.SQLName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// No explicit PK constraint, check for columns with IsPrimaryKey = true
|
||||||
|
for _, col := range table.Columns {
|
||||||
|
if col.IsPrimaryKey {
|
||||||
|
columnNames = append(columnNames, col.SQLName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort for consistent output
|
||||||
|
sort.Strings(columnNames)
|
||||||
|
}
|
||||||
|
|
||||||
if len(columnNames) == 0 {
|
if len(columnNames) == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -503,15 +595,24 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
|||||||
indexName = fmt.Sprintf("%s_%s_%s", indexType, schema.SQLName(), table.SQLName())
|
indexName = fmt.Sprintf("%s_%s_%s", indexType, schema.SQLName(), table.SQLName())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build column list
|
// Build column list with operator class support for GIN indexes
|
||||||
columnNames := make([]string, 0, len(index.Columns))
|
columnExprs := make([]string, 0, len(index.Columns))
|
||||||
for _, colName := range index.Columns {
|
for _, colName := range index.Columns {
|
||||||
if col, ok := table.Columns[colName]; ok {
|
if col, ok := table.Columns[colName]; ok {
|
||||||
columnNames = append(columnNames, col.SQLName())
|
colExpr := col.SQLName()
|
||||||
|
// For GIN indexes on text columns, add operator class
|
||||||
|
if strings.EqualFold(index.Type, "gin") && isTextType(col.Type) {
|
||||||
|
opClass := extractOperatorClass(index.Comment)
|
||||||
|
if opClass == "" {
|
||||||
|
opClass = "gin_trgm_ops"
|
||||||
|
}
|
||||||
|
colExpr = fmt.Sprintf("%s %s", col.SQLName(), opClass)
|
||||||
|
}
|
||||||
|
columnExprs = append(columnExprs, colExpr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(columnNames) == 0 {
|
if len(columnExprs) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,10 +621,20 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
|||||||
unique = "UNIQUE "
|
unique = "UNIQUE "
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexType := index.Type
|
||||||
|
if indexType == "" {
|
||||||
|
indexType = "btree"
|
||||||
|
}
|
||||||
|
|
||||||
|
whereClause := ""
|
||||||
|
if index.Where != "" {
|
||||||
|
whereClause = fmt.Sprintf(" WHERE %s", index.Where)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintf(w.writer, "CREATE %sINDEX IF NOT EXISTS %s\n",
|
fmt.Fprintf(w.writer, "CREATE %sINDEX IF NOT EXISTS %s\n",
|
||||||
unique, indexName)
|
unique, indexName)
|
||||||
fmt.Fprintf(w.writer, " ON %s.%s USING btree (%s);\n\n",
|
fmt.Fprintf(w.writer, " ON %s.%s USING %s (%s)%s;\n\n",
|
||||||
schema.SQLName(), table.SQLName(), strings.Join(columnNames, ", "))
|
schema.SQLName(), table.SQLName(), indexType, strings.Join(columnExprs, ", "), whereClause)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,11 +829,46 @@ func isIntegerType(colType string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isTextType checks if a column type is a text type (for GIN index operator class)
|
||||||
|
func isTextType(colType string) bool {
|
||||||
|
textTypes := []string{"text", "varchar", "character varying", "char", "character", "string"}
|
||||||
|
lowerType := strings.ToLower(colType)
|
||||||
|
for _, t := range textTypes {
|
||||||
|
if strings.HasPrefix(lowerType, t) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractOperatorClass extracts operator class from index comment/note
|
||||||
|
// Looks for common operator classes like gin_trgm_ops, gist_trgm_ops, etc.
|
||||||
|
func extractOperatorClass(comment string) string {
|
||||||
|
if comment == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
lowerComment := strings.ToLower(comment)
|
||||||
|
// Common GIN/GiST operator classes
|
||||||
|
opClasses := []string{"gin_trgm_ops", "gist_trgm_ops", "gin_bigm_ops", "jsonb_ops", "jsonb_path_ops", "array_ops"}
|
||||||
|
for _, op := range opClasses {
|
||||||
|
if strings.Contains(lowerComment, op) {
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// escapeQuote escapes single quotes in strings for SQL
|
// escapeQuote escapes single quotes in strings for SQL
|
||||||
func escapeQuote(s string) string {
|
func escapeQuote(s string) string {
|
||||||
return strings.ReplaceAll(s, "'", "''")
|
return strings.ReplaceAll(s, "'", "''")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stripBackticks removes backticks from SQL expressions
|
||||||
|
// DBML uses backticks for SQL expressions like `now()`, but PostgreSQL doesn't use backticks
|
||||||
|
func stripBackticks(s string) string {
|
||||||
|
return strings.ReplaceAll(s, "`", "")
|
||||||
|
}
|
||||||
|
|
||||||
// extractSequenceName extracts sequence name from nextval() expression
|
// extractSequenceName extracts sequence name from nextval() expression
|
||||||
// Example: "nextval('public.users_id_seq'::regclass)" returns "users_id_seq"
|
// Example: "nextval('public.users_id_seq'::regclass)" returns "users_id_seq"
|
||||||
func extractSequenceName(defaultExpr string) string {
|
func extractSequenceName(defaultExpr string) string {
|
||||||
@@ -745,3 +891,195 @@ func extractSequenceName(defaultExpr string) string {
|
|||||||
}
|
}
|
||||||
return fullName
|
return fullName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// executeDatabaseSQL executes SQL statements directly on a PostgreSQL database
|
||||||
|
func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) error {
|
||||||
|
// Initialize execution report
|
||||||
|
w.executionReport = &ExecutionReport{
|
||||||
|
StartTime: getCurrentTimestamp(),
|
||||||
|
Schemas: make([]SchemaReport, 0),
|
||||||
|
Errors: make([]ExecutionError, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate SQL statements
|
||||||
|
statements, err := w.GenerateDatabaseStatements(db)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate SQL statements: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.executionReport.TotalStatements = len(statements)
|
||||||
|
|
||||||
|
// Connect to database
|
||||||
|
ctx := context.Background()
|
||||||
|
conn, err := pgx.Connect(ctx, connString)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to database: %w", err)
|
||||||
|
}
|
||||||
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
|
// Track schemas and tables
|
||||||
|
schemaMap := make(map[string]*SchemaReport)
|
||||||
|
currentSchema := ""
|
||||||
|
|
||||||
|
// Execute each statement
|
||||||
|
for i, stmt := range statements {
|
||||||
|
stmtTrimmed := strings.TrimSpace(stmt)
|
||||||
|
|
||||||
|
// Skip comments
|
||||||
|
if strings.HasPrefix(stmtTrimmed, "--") {
|
||||||
|
// Check if this is a schema comment to track schema changes
|
||||||
|
if strings.Contains(stmtTrimmed, "Schema:") {
|
||||||
|
parts := strings.Split(stmtTrimmed, "Schema:")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
currentSchema = strings.TrimSpace(parts[1])
|
||||||
|
if _, exists := schemaMap[currentSchema]; !exists {
|
||||||
|
schemaReport := SchemaReport{
|
||||||
|
Name: currentSchema,
|
||||||
|
Tables: make([]TableReport, 0),
|
||||||
|
}
|
||||||
|
schemaMap[currentSchema] = &schemaReport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip empty statements
|
||||||
|
if stmtTrimmed == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Executing statement %d/%d...\n", i+1, len(statements))
|
||||||
|
|
||||||
|
_, execErr := conn.Exec(ctx, stmt)
|
||||||
|
if execErr != nil {
|
||||||
|
w.executionReport.FailedStatements++
|
||||||
|
execError := ExecutionError{
|
||||||
|
StatementNumber: i + 1,
|
||||||
|
Statement: truncateStatement(stmt),
|
||||||
|
Error: execErr.Error(),
|
||||||
|
}
|
||||||
|
w.executionReport.Errors = append(w.executionReport.Errors, execError)
|
||||||
|
|
||||||
|
// Track table creation failure
|
||||||
|
if strings.Contains(strings.ToUpper(stmtTrimmed), "CREATE TABLE") && currentSchema != "" {
|
||||||
|
tableName := extractTableNameFromCreate(stmtTrimmed)
|
||||||
|
if tableName != "" && schemaMap[currentSchema] != nil {
|
||||||
|
schemaMap[currentSchema].Tables = append(schemaMap[currentSchema].Tables, TableReport{
|
||||||
|
Name: tableName,
|
||||||
|
Created: false,
|
||||||
|
Error: execErr.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue with next statement instead of failing completely
|
||||||
|
fmt.Fprintf(os.Stderr, "⚠ Warning: Statement %d failed: %v\n", i+1, execErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
w.executionReport.ExecutedStatements++
|
||||||
|
|
||||||
|
// Track successful table creation
|
||||||
|
if strings.Contains(strings.ToUpper(stmtTrimmed), "CREATE TABLE") && currentSchema != "" {
|
||||||
|
tableName := extractTableNameFromCreate(stmtTrimmed)
|
||||||
|
if tableName != "" && schemaMap[currentSchema] != nil {
|
||||||
|
schemaMap[currentSchema].Tables = append(schemaMap[currentSchema].Tables, TableReport{
|
||||||
|
Name: tableName,
|
||||||
|
Created: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert schema map to slice
|
||||||
|
for _, schemaReport := range schemaMap {
|
||||||
|
w.executionReport.Schemas = append(w.executionReport.Schemas, *schemaReport)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.executionReport.EndTime = getCurrentTimestamp()
|
||||||
|
|
||||||
|
// Write report if path is specified
|
||||||
|
if reportPath, ok := w.options.Metadata["report_path"].(string); ok && reportPath != "" {
|
||||||
|
if err := w.writeReport(reportPath); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "⚠ Warning: Failed to write report: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "✓ Report written to: %s\n", reportPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.executionReport.FailedStatements > 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "⚠ Completed with %d errors out of %d statements\n",
|
||||||
|
w.executionReport.FailedStatements, w.executionReport.TotalStatements)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "✓ Successfully executed %d statements\n", w.executionReport.ExecutedStatements)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeReport writes the execution report to a JSON file
|
||||||
|
func (w *Writer) writeReport(reportPath string) error {
|
||||||
|
file, err := os.Create(reportPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create report file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(file)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
if err := encoder.Encode(w.executionReport); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTableNameFromCreate extracts table name from CREATE TABLE statement
|
||||||
|
func extractTableNameFromCreate(stmt string) string {
|
||||||
|
// Match: CREATE TABLE [IF NOT EXISTS] schema.table_name or table_name
|
||||||
|
upper := strings.ToUpper(stmt)
|
||||||
|
idx := strings.Index(upper, "CREATE TABLE")
|
||||||
|
if idx == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
rest := strings.TrimSpace(stmt[idx+12:]) // Skip "CREATE TABLE"
|
||||||
|
|
||||||
|
// Skip "IF NOT EXISTS"
|
||||||
|
if strings.HasPrefix(strings.ToUpper(rest), "IF NOT EXISTS") {
|
||||||
|
rest = strings.TrimSpace(rest[13:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the table name (first token before '(' or whitespace)
|
||||||
|
tokens := strings.FieldsFunc(rest, func(r rune) bool {
|
||||||
|
return r == '(' || r == ' ' || r == '\n' || r == '\t'
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(tokens) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle schema.table format
|
||||||
|
fullName := tokens[0]
|
||||||
|
parts := strings.Split(fullName, ".")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
return parts[len(parts)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullName
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncateStatement truncates long SQL statements for error messages
|
||||||
|
func truncateStatement(stmt string) string {
|
||||||
|
const maxLen = 200
|
||||||
|
if len(stmt) <= maxLen {
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
return stmt[:maxLen] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCurrentTimestamp returns the current timestamp in a readable format
|
||||||
|
func getCurrentTimestamp() string {
|
||||||
|
return time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|||||||
346
vendor/modules.txt
vendored
346
vendor/modules.txt
vendored
@@ -1,6 +1,92 @@
|
|||||||
|
# 4d63.com/gocheckcompilerdirectives v1.3.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# 4d63.com/gochecknoglobals v0.2.2
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/4meepo/tagalign v1.4.2
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/Abirdcfly/dupword v0.1.3
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/Antonboom/errname v1.0.0
|
||||||
|
## explicit; go 1.22.1
|
||||||
|
# github.com/Antonboom/nilnil v1.0.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/Antonboom/testifylint v1.5.2
|
||||||
|
## explicit; go 1.22.1
|
||||||
|
# github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/Crocmagnon/fatcontext v0.7.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24
|
||||||
|
## explicit; go 1.13
|
||||||
|
# github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
# github.com/Masterminds/semver/v3 v3.3.0
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/OpenPeeDeeP/depguard/v2 v2.2.1
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
# github.com/alecthomas/go-check-sumtype v0.3.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/alexkohler/nakedret/v2 v2.0.5
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/alexkohler/prealloc v1.0.0
|
||||||
|
## explicit; go 1.15
|
||||||
|
# github.com/alingse/asasalint v0.0.11
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/alingse/nilnesserr v0.1.2
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ashanbrown/forbidigo v1.6.0
|
||||||
|
## explicit; go 1.13
|
||||||
|
# github.com/ashanbrown/makezero v1.2.0
|
||||||
|
## explicit; go 1.12
|
||||||
|
# github.com/beorn7/perks v1.0.1
|
||||||
|
## explicit; go 1.11
|
||||||
|
# github.com/bkielbasa/cyclop v1.2.3
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/blizzy78/varnamelen v0.8.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/bombsimon/wsl/v4 v4.5.0
|
||||||
|
## explicit; go 1.22
|
||||||
|
# github.com/breml/bidichk v0.3.2
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/breml/errchkjson v0.4.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/butuzov/ireturn v0.3.1
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/butuzov/mirror v1.3.0
|
||||||
|
## explicit; go 1.19
|
||||||
|
# github.com/catenacyber/perfsprint v0.8.2
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ccojocar/zxcvbn-go v1.0.2
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/cespare/xxhash/v2 v2.3.0
|
||||||
|
## explicit; go 1.11
|
||||||
|
# github.com/charithe/durationcheck v0.0.10
|
||||||
|
## explicit; go 1.14
|
||||||
|
# github.com/chavacava/garif v0.1.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/ckaznocha/intrange v0.3.0
|
||||||
|
## explicit; go 1.22
|
||||||
|
# github.com/curioswitch/go-reassign v0.3.0
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/daixiang0/gci v0.13.5
|
||||||
|
## explicit; go 1.21
|
||||||
# github.com/davecgh/go-spew v1.1.1
|
# github.com/davecgh/go-spew v1.1.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/davecgh/go-spew/spew
|
github.com/davecgh/go-spew/spew
|
||||||
|
# github.com/denis-tingaikin/go-header v0.5.0
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/ettle/strcase v0.2.0
|
||||||
|
## explicit; go 1.12
|
||||||
|
# github.com/fatih/color v1.18.0
|
||||||
|
## explicit; go 1.17
|
||||||
|
# github.com/fatih/structtag v1.2.0
|
||||||
|
## explicit; go 1.12
|
||||||
|
# github.com/firefart/nonamedreturns v1.0.5
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/fsnotify/fsnotify v1.5.4
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/fzipp/gocyclo v0.6.0
|
||||||
|
## explicit; go 1.18
|
||||||
# github.com/gdamore/encoding v1.0.1
|
# github.com/gdamore/encoding v1.0.1
|
||||||
## explicit; go 1.9
|
## explicit; go 1.9
|
||||||
github.com/gdamore/encoding
|
github.com/gdamore/encoding
|
||||||
@@ -44,9 +130,75 @@ github.com/gdamore/tcell/v2/terminfo/x/xfce
|
|||||||
github.com/gdamore/tcell/v2/terminfo/x/xterm
|
github.com/gdamore/tcell/v2/terminfo/x/xterm
|
||||||
github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty
|
github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty
|
||||||
github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty
|
github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty
|
||||||
|
# github.com/ghostiam/protogetter v0.3.9
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/go-critic/go-critic v0.12.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/go-toolsmith/astcast v1.1.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/go-toolsmith/astcopy v1.1.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/go-toolsmith/astequal v1.2.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/go-toolsmith/astfmt v1.1.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/go-toolsmith/astp v1.1.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/go-toolsmith/strparse v1.1.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/go-toolsmith/typep v1.1.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/go-viper/mapstructure/v2 v2.2.1
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/go-xmlfmt/xmlfmt v1.1.3
|
||||||
|
## explicit
|
||||||
|
# github.com/gobwas/glob v0.2.3
|
||||||
|
## explicit
|
||||||
|
# github.com/gofrs/flock v0.12.1
|
||||||
|
## explicit; go 1.21.0
|
||||||
|
# github.com/golang/protobuf v1.5.3
|
||||||
|
## explicit; go 1.9
|
||||||
|
# github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/golangci/go-printf-func-name v0.1.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/golangci/golangci-lint v1.64.8
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
# github.com/golangci/misspell v0.6.0
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/golangci/plugin-module-register v0.1.1
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/golangci/revgrep v0.8.0
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/google/go-cmp v0.7.0
|
||||||
|
## explicit; go 1.21
|
||||||
# github.com/google/uuid v1.6.0
|
# github.com/google/uuid v1.6.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/google/uuid
|
github.com/google/uuid
|
||||||
|
# github.com/gordonklaus/ineffassign v0.1.0
|
||||||
|
## explicit; go 1.14
|
||||||
|
# github.com/gostaticanalysis/analysisutil v0.7.1
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/gostaticanalysis/comment v1.5.0
|
||||||
|
## explicit; go 1.22.9
|
||||||
|
# github.com/gostaticanalysis/forcetypeassert v0.2.0
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
# github.com/gostaticanalysis/nilerr v0.1.1
|
||||||
|
## explicit; go 1.15
|
||||||
|
# github.com/hashicorp/go-immutable-radix/v2 v2.1.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/hashicorp/go-version v1.7.0
|
||||||
|
## explicit
|
||||||
|
# github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/hashicorp/hcl v1.0.0
|
||||||
|
## explicit
|
||||||
|
# github.com/hexops/gotextdiff v1.0.3
|
||||||
|
## explicit; go 1.16
|
||||||
# github.com/inconshreveable/mousetrap v1.1.0
|
# github.com/inconshreveable/mousetrap v1.1.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/inconshreveable/mousetrap
|
github.com/inconshreveable/mousetrap
|
||||||
@@ -68,23 +220,115 @@ github.com/jackc/pgx/v5/pgconn/ctxwatch
|
|||||||
github.com/jackc/pgx/v5/pgconn/internal/bgreader
|
github.com/jackc/pgx/v5/pgconn/internal/bgreader
|
||||||
github.com/jackc/pgx/v5/pgproto3
|
github.com/jackc/pgx/v5/pgproto3
|
||||||
github.com/jackc/pgx/v5/pgtype
|
github.com/jackc/pgx/v5/pgtype
|
||||||
|
# github.com/jgautheron/goconst v1.7.1
|
||||||
|
## explicit; go 1.13
|
||||||
|
# github.com/jingyugao/rowserrcheck v1.1.1
|
||||||
|
## explicit; go 1.13
|
||||||
# github.com/jinzhu/inflection v1.0.0
|
# github.com/jinzhu/inflection v1.0.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/jinzhu/inflection
|
github.com/jinzhu/inflection
|
||||||
|
# github.com/jjti/go-spancheck v0.6.4
|
||||||
|
## explicit; go 1.22.1
|
||||||
|
# github.com/julz/importas v0.2.0
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/karamaru-alpha/copyloopvar v1.2.1
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/kisielk/errcheck v1.9.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/kkHAIKE/contextcheck v1.1.6
|
||||||
|
## explicit; go 1.23.0
|
||||||
# github.com/kr/pretty v0.3.1
|
# github.com/kr/pretty v0.3.1
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
|
# github.com/kulti/thelper v0.6.3
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/kunwardeep/paralleltest v1.0.10
|
||||||
|
## explicit; go 1.17
|
||||||
|
# github.com/lasiar/canonicalheader v1.1.2
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ldez/exptostd v0.4.2
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ldez/gomoddirectives v0.6.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ldez/grignotin v0.9.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ldez/tagliatelle v0.7.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ldez/usetesting v0.4.2
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/leonklingele/grouper v1.1.2
|
||||||
|
## explicit; go 1.18
|
||||||
# github.com/lucasb-eyer/go-colorful v1.2.0
|
# github.com/lucasb-eyer/go-colorful v1.2.0
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/lucasb-eyer/go-colorful
|
github.com/lucasb-eyer/go-colorful
|
||||||
|
# github.com/macabu/inamedparam v0.1.3
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/magiconair/properties v1.8.6
|
||||||
|
## explicit; go 1.13
|
||||||
|
# github.com/maratori/testableexamples v1.0.0
|
||||||
|
## explicit; go 1.19
|
||||||
|
# github.com/maratori/testpackage v1.1.1
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/matoous/godox v1.1.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/mattn/go-colorable v0.1.14
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/mattn/go-isatty v0.0.20
|
||||||
|
## explicit; go 1.15
|
||||||
# github.com/mattn/go-runewidth v0.0.16
|
# github.com/mattn/go-runewidth v0.0.16
|
||||||
## explicit; go 1.9
|
## explicit; go 1.9
|
||||||
github.com/mattn/go-runewidth
|
github.com/mattn/go-runewidth
|
||||||
|
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||||
|
## explicit
|
||||||
|
# github.com/mgechev/revive v1.7.0
|
||||||
|
## explicit; go 1.22.1
|
||||||
|
# github.com/mitchellh/go-homedir v1.1.0
|
||||||
|
## explicit
|
||||||
|
# github.com/mitchellh/mapstructure v1.5.0
|
||||||
|
## explicit; go 1.14
|
||||||
|
# github.com/moricho/tparallel v0.3.2
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/nakabonne/nestif v0.3.1
|
||||||
|
## explicit; go 1.15
|
||||||
|
# github.com/nishanths/exhaustive v0.12.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/nishanths/predeclared v0.2.2
|
||||||
|
## explicit; go 1.14
|
||||||
|
# github.com/nunnatsa/ginkgolinter v0.19.1
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
# github.com/olekukonko/tablewriter v0.0.5
|
||||||
|
## explicit; go 1.12
|
||||||
|
# github.com/pelletier/go-toml v1.9.5
|
||||||
|
## explicit; go 1.12
|
||||||
|
# github.com/pelletier/go-toml/v2 v2.2.3
|
||||||
|
## explicit; go 1.21.0
|
||||||
# github.com/pmezard/go-difflib v1.0.0
|
# github.com/pmezard/go-difflib v1.0.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/pmezard/go-difflib/difflib
|
github.com/pmezard/go-difflib/difflib
|
||||||
|
# github.com/polyfloyd/go-errorlint v1.7.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/prometheus/client_golang v1.12.1
|
||||||
|
## explicit; go 1.13
|
||||||
|
# github.com/prometheus/client_model v0.2.0
|
||||||
|
## explicit; go 1.9
|
||||||
|
# github.com/prometheus/common v0.32.1
|
||||||
|
## explicit; go 1.13
|
||||||
|
# github.com/prometheus/procfs v0.7.3
|
||||||
|
## explicit; go 1.13
|
||||||
# github.com/puzpuzpuz/xsync/v3 v3.5.1
|
# github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/puzpuzpuz/xsync/v3
|
github.com/puzpuzpuz/xsync/v3
|
||||||
|
# github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1
|
||||||
|
## explicit; go 1.19
|
||||||
|
# github.com/quasilyte/go-ruleguard/dsl v0.3.22
|
||||||
|
## explicit; go 1.15
|
||||||
|
# github.com/quasilyte/gogrep v0.5.0
|
||||||
|
## explicit; go 1.16
|
||||||
|
# github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727
|
||||||
|
## explicit; go 1.14
|
||||||
|
# github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567
|
||||||
|
## explicit; go 1.17
|
||||||
|
# github.com/raeperd/recvcheck v0.2.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
# github.com/rivo/tview v0.42.0
|
# github.com/rivo/tview v0.42.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/rivo/tview
|
github.com/rivo/tview
|
||||||
@@ -93,20 +337,76 @@ github.com/rivo/tview
|
|||||||
github.com/rivo/uniseg
|
github.com/rivo/uniseg
|
||||||
# github.com/rogpeppe/go-internal v1.14.1
|
# github.com/rogpeppe/go-internal v1.14.1
|
||||||
## explicit; go 1.23
|
## explicit; go 1.23
|
||||||
|
# github.com/ryancurrah/gomodguard v1.3.5
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ryanrolds/sqlclosecheck v0.5.1
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/sanposhiho/wastedassign/v2 v2.1.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/sashamelentyev/interfacebloat v1.1.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/sashamelentyev/usestdlibvars v1.28.0
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/securego/gosec/v2 v2.22.2
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
# github.com/sirupsen/logrus v1.9.3
|
||||||
|
## explicit; go 1.13
|
||||||
|
# github.com/sivchari/containedctx v1.0.3
|
||||||
|
## explicit; go 1.17
|
||||||
|
# github.com/sivchari/tenv v1.12.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/sonatard/noctx v0.1.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/sourcegraph/go-diff v0.7.0
|
||||||
|
## explicit; go 1.14
|
||||||
|
# github.com/spf13/afero v1.12.0
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/spf13/cast v1.5.0
|
||||||
|
## explicit; go 1.18
|
||||||
# github.com/spf13/cobra v1.10.2
|
# github.com/spf13/cobra v1.10.2
|
||||||
## explicit; go 1.15
|
## explicit; go 1.15
|
||||||
github.com/spf13/cobra
|
github.com/spf13/cobra
|
||||||
|
# github.com/spf13/jwalterweatherman v1.1.0
|
||||||
|
## explicit
|
||||||
# github.com/spf13/pflag v1.0.10
|
# github.com/spf13/pflag v1.0.10
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/spf13/pflag
|
github.com/spf13/pflag
|
||||||
|
# github.com/spf13/viper v1.12.0
|
||||||
|
## explicit; go 1.17
|
||||||
|
# github.com/ssgreg/nlreturn/v2 v2.2.1
|
||||||
|
## explicit; go 1.13
|
||||||
|
# github.com/stbenjam/no-sprintf-host-port v0.2.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/stretchr/objx v0.5.2
|
||||||
|
## explicit; go 1.20
|
||||||
# github.com/stretchr/testify v1.11.1
|
# github.com/stretchr/testify v1.11.1
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/stretchr/testify/assert
|
github.com/stretchr/testify/assert
|
||||||
github.com/stretchr/testify/assert/yaml
|
github.com/stretchr/testify/assert/yaml
|
||||||
github.com/stretchr/testify/require
|
github.com/stretchr/testify/require
|
||||||
|
# github.com/subosito/gotenv v1.4.1
|
||||||
|
## explicit; go 1.18
|
||||||
|
# github.com/tdakkota/asciicheck v0.4.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/tetafro/godot v1.5.0
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3
|
||||||
|
## explicit; go 1.12
|
||||||
|
# github.com/timonwong/loggercheck v0.10.1
|
||||||
|
## explicit; go 1.22.0
|
||||||
# github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
|
# github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tmthrgd/go-hex
|
github.com/tmthrgd/go-hex
|
||||||
|
# github.com/tomarrell/wrapcheck/v2 v2.10.0
|
||||||
|
## explicit; go 1.21
|
||||||
|
# github.com/tommy-muehle/go-mnd/v2 v2.5.1
|
||||||
|
## explicit; go 1.12
|
||||||
|
# github.com/ultraware/funlen v0.2.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# github.com/ultraware/whitespace v0.2.0
|
||||||
|
## explicit; go 1.20
|
||||||
# github.com/uptrace/bun v1.2.16
|
# github.com/uptrace/bun v1.2.16
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.24.0
|
||||||
github.com/uptrace/bun
|
github.com/uptrace/bun
|
||||||
@@ -118,6 +418,10 @@ github.com/uptrace/bun/internal
|
|||||||
github.com/uptrace/bun/internal/parser
|
github.com/uptrace/bun/internal/parser
|
||||||
github.com/uptrace/bun/internal/tagparser
|
github.com/uptrace/bun/internal/tagparser
|
||||||
github.com/uptrace/bun/schema
|
github.com/uptrace/bun/schema
|
||||||
|
# github.com/uudashr/gocognit v1.2.0
|
||||||
|
## explicit; go 1.19
|
||||||
|
# github.com/uudashr/iface v1.3.1
|
||||||
|
## explicit; go 1.22.1
|
||||||
# github.com/vmihailenco/msgpack/v5 v5.4.1
|
# github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/vmihailenco/msgpack/v5
|
github.com/vmihailenco/msgpack/v5
|
||||||
@@ -127,9 +431,37 @@ github.com/vmihailenco/msgpack/v5/msgpcode
|
|||||||
github.com/vmihailenco/tagparser/v2
|
github.com/vmihailenco/tagparser/v2
|
||||||
github.com/vmihailenco/tagparser/v2/internal
|
github.com/vmihailenco/tagparser/v2/internal
|
||||||
github.com/vmihailenco/tagparser/v2/internal/parser
|
github.com/vmihailenco/tagparser/v2/internal/parser
|
||||||
|
# github.com/xen0n/gosmopolitan v1.2.2
|
||||||
|
## explicit; go 1.19
|
||||||
|
# github.com/yagipy/maintidx v1.0.0
|
||||||
|
## explicit; go 1.17
|
||||||
|
# github.com/yeya24/promlinter v0.3.0
|
||||||
|
## explicit; go 1.20
|
||||||
|
# github.com/ykadowak/zerologlint v0.1.5
|
||||||
|
## explicit; go 1.19
|
||||||
|
# gitlab.com/bosi/decorder v0.4.2
|
||||||
|
## explicit; go 1.20
|
||||||
|
# go-simpler.org/musttag v0.13.0
|
||||||
|
## explicit; go 1.20
|
||||||
|
# go-simpler.org/sloglint v0.9.0
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
# go.uber.org/atomic v1.7.0
|
||||||
|
## explicit; go 1.13
|
||||||
|
# go.uber.org/automaxprocs v1.6.0
|
||||||
|
## explicit; go 1.20
|
||||||
|
# go.uber.org/multierr v1.6.0
|
||||||
|
## explicit; go 1.12
|
||||||
|
# go.uber.org/zap v1.24.0
|
||||||
|
## explicit; go 1.19
|
||||||
# golang.org/x/crypto v0.41.0
|
# golang.org/x/crypto v0.41.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
golang.org/x/crypto/pbkdf2
|
golang.org/x/crypto/pbkdf2
|
||||||
|
# golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac
|
||||||
|
## explicit; go 1.18
|
||||||
|
# golang.org/x/mod v0.26.0
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
# golang.org/x/sync v0.16.0
|
||||||
|
## explicit; go 1.23.0
|
||||||
# golang.org/x/sys v0.38.0
|
# golang.org/x/sys v0.38.0
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.24.0
|
||||||
golang.org/x/sys/cpu
|
golang.org/x/sys/cpu
|
||||||
@@ -156,6 +488,20 @@ golang.org/x/text/transform
|
|||||||
golang.org/x/text/unicode/bidi
|
golang.org/x/text/unicode/bidi
|
||||||
golang.org/x/text/unicode/norm
|
golang.org/x/text/unicode/norm
|
||||||
golang.org/x/text/width
|
golang.org/x/text/width
|
||||||
|
# golang.org/x/tools v0.35.0
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
# google.golang.org/protobuf v1.36.5
|
||||||
|
## explicit; go 1.21
|
||||||
|
# gopkg.in/ini.v1 v1.67.0
|
||||||
|
## explicit
|
||||||
|
# gopkg.in/yaml.v2 v2.4.0
|
||||||
|
## explicit; go 1.15
|
||||||
# gopkg.in/yaml.v3 v3.0.1
|
# gopkg.in/yaml.v3 v3.0.1
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/yaml.v3
|
gopkg.in/yaml.v3
|
||||||
|
# honnef.co/go/tools v0.6.1
|
||||||
|
## explicit; go 1.23
|
||||||
|
# mvdan.cc/gofumpt v0.7.0
|
||||||
|
## explicit; go 1.22
|
||||||
|
# mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f
|
||||||
|
## explicit; go 1.21
|
||||||
|
|||||||
Reference in New Issue
Block a user