Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d2884f5cf | ||
|
|
f192decff8 | ||
|
|
8b906cf4a3 | ||
|
|
0a3966e6fc |
@@ -286,79 +286,79 @@ func readDatabaseForConvert(dbType, filePath, connString string) (*models.Databa
|
|||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for DBML format")
|
return nil, fmt.Errorf("file path is required for DBML format")
|
||||||
}
|
}
|
||||||
reader = dbml.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = dbml.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "dctx":
|
case "dctx":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for DCTX format")
|
return nil, fmt.Errorf("file path is required for DCTX format")
|
||||||
}
|
}
|
||||||
reader = dctx.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = dctx.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "drawdb":
|
case "drawdb":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for DrawDB format")
|
return nil, fmt.Errorf("file path is required for DrawDB format")
|
||||||
}
|
}
|
||||||
reader = drawdb.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = drawdb.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "json":
|
case "json":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for JSON format")
|
return nil, fmt.Errorf("file path is required for JSON format")
|
||||||
}
|
}
|
||||||
reader = json.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = json.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "yaml", "yml":
|
case "yaml", "yml":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for YAML format")
|
return nil, fmt.Errorf("file path is required for YAML format")
|
||||||
}
|
}
|
||||||
reader = yaml.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = yaml.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "pgsql", "postgres", "postgresql":
|
case "pgsql", "postgres", "postgresql":
|
||||||
if connString == "" {
|
if connString == "" {
|
||||||
return nil, fmt.Errorf("connection string is required for PostgreSQL format")
|
return nil, fmt.Errorf("connection string is required for PostgreSQL format")
|
||||||
}
|
}
|
||||||
reader = pgsql.NewReader(&readers.ReaderOptions{ConnectionString: connString})
|
reader = pgsql.NewReader(newReaderOptions("", connString))
|
||||||
|
|
||||||
case "gorm":
|
case "gorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for GORM format")
|
return nil, fmt.Errorf("file path is required for GORM format")
|
||||||
}
|
}
|
||||||
reader = gorm.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = gorm.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "bun":
|
case "bun":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for Bun format")
|
return nil, fmt.Errorf("file path is required for Bun format")
|
||||||
}
|
}
|
||||||
reader = bun.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = bun.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "drizzle":
|
case "drizzle":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for Drizzle format")
|
return nil, fmt.Errorf("file path is required for Drizzle format")
|
||||||
}
|
}
|
||||||
reader = drizzle.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = drizzle.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "prisma":
|
case "prisma":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for Prisma format")
|
return nil, fmt.Errorf("file path is required for Prisma format")
|
||||||
}
|
}
|
||||||
reader = prisma.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = prisma.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "typeorm":
|
case "typeorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for TypeORM format")
|
return nil, fmt.Errorf("file path is required for TypeORM format")
|
||||||
}
|
}
|
||||||
reader = typeorm.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = typeorm.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "graphql", "gql":
|
case "graphql", "gql":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for GraphQL format")
|
return nil, fmt.Errorf("file path is required for GraphQL format")
|
||||||
}
|
}
|
||||||
reader = graphql.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = graphql.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "mssql", "sqlserver", "mssql2016", "mssql2017", "mssql2019", "mssql2022":
|
case "mssql", "sqlserver", "mssql2016", "mssql2017", "mssql2019", "mssql2022":
|
||||||
if connString == "" {
|
if connString == "" {
|
||||||
return nil, fmt.Errorf("connection string is required for MSSQL format")
|
return nil, fmt.Errorf("connection string is required for MSSQL format")
|
||||||
}
|
}
|
||||||
reader = mssql.NewReader(&readers.ReaderOptions{ConnectionString: connString})
|
reader = mssql.NewReader(newReaderOptions("", connString))
|
||||||
|
|
||||||
case "sqlite", "sqlite3":
|
case "sqlite", "sqlite3":
|
||||||
// SQLite can use either file path or connection string
|
// SQLite can use either file path or connection string
|
||||||
@@ -369,7 +369,7 @@ func readDatabaseForConvert(dbType, filePath, connString string) (*models.Databa
|
|||||||
if dbPath == "" {
|
if dbPath == "" {
|
||||||
return nil, fmt.Errorf("file path or connection string is required for SQLite format")
|
return nil, fmt.Errorf("file path or connection string is required for SQLite format")
|
||||||
}
|
}
|
||||||
reader = sqlite.NewReader(&readers.ReaderOptions{FilePath: dbPath})
|
reader = sqlite.NewReader(newReaderOptions(dbPath, ""))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported source format: %s", dbType)
|
return nil, fmt.Errorf("unsupported source format: %s", dbType)
|
||||||
@@ -386,12 +386,7 @@ func readDatabaseForConvert(dbType, filePath, connString string) (*models.Databa
|
|||||||
func writeDatabase(db *models.Database, dbType, outputPath, packageName, schemaFilter string, flattenSchema bool, nullableTypes string) error {
|
func writeDatabase(db *models.Database, dbType, outputPath, packageName, schemaFilter string, flattenSchema bool, nullableTypes string) error {
|
||||||
var writer writers.Writer
|
var writer writers.Writer
|
||||||
|
|
||||||
writerOpts := &writers.WriterOptions{
|
writerOpts := newWriterOptions(outputPath, packageName, flattenSchema, nullableTypes)
|
||||||
OutputPath: outputPath,
|
|
||||||
PackageName: packageName,
|
|
||||||
FlattenSchema: flattenSchema,
|
|
||||||
NullableTypes: nullableTypes,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(dbType) {
|
switch strings.ToLower(dbType) {
|
||||||
case "dbml":
|
case "dbml":
|
||||||
|
|||||||
@@ -240,62 +240,62 @@ func readDatabaseForEdit(dbType, filePath, connString, label string) (*models.Da
|
|||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for DBML format", label)
|
return nil, fmt.Errorf("%s: file path is required for DBML format", label)
|
||||||
}
|
}
|
||||||
reader = dbml.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = dbml.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "dctx":
|
case "dctx":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for DCTX format", label)
|
return nil, fmt.Errorf("%s: file path is required for DCTX format", label)
|
||||||
}
|
}
|
||||||
reader = dctx.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = dctx.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "drawdb":
|
case "drawdb":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for DrawDB format", label)
|
return nil, fmt.Errorf("%s: file path is required for DrawDB format", label)
|
||||||
}
|
}
|
||||||
reader = drawdb.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = drawdb.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "graphql":
|
case "graphql":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for GraphQL format", label)
|
return nil, fmt.Errorf("%s: file path is required for GraphQL format", label)
|
||||||
}
|
}
|
||||||
reader = graphql.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = graphql.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "json":
|
case "json":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for JSON format", label)
|
return nil, fmt.Errorf("%s: file path is required for JSON format", label)
|
||||||
}
|
}
|
||||||
reader = json.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = json.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "yaml":
|
case "yaml":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for YAML format", label)
|
return nil, fmt.Errorf("%s: file path is required for YAML format", label)
|
||||||
}
|
}
|
||||||
reader = yaml.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = yaml.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "gorm":
|
case "gorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for GORM format", label)
|
return nil, fmt.Errorf("%s: file path is required for GORM format", label)
|
||||||
}
|
}
|
||||||
reader = gorm.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = gorm.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "bun":
|
case "bun":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for Bun format", label)
|
return nil, fmt.Errorf("%s: file path is required for Bun format", label)
|
||||||
}
|
}
|
||||||
reader = bun.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = bun.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "drizzle":
|
case "drizzle":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for Drizzle format", label)
|
return nil, fmt.Errorf("%s: file path is required for Drizzle format", label)
|
||||||
}
|
}
|
||||||
reader = drizzle.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = drizzle.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "prisma":
|
case "prisma":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for Prisma format", label)
|
return nil, fmt.Errorf("%s: file path is required for Prisma format", label)
|
||||||
}
|
}
|
||||||
reader = prisma.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = prisma.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "typeorm":
|
case "typeorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for TypeORM format", label)
|
return nil, fmt.Errorf("%s: file path is required for TypeORM format", label)
|
||||||
}
|
}
|
||||||
reader = typeorm.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = typeorm.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "pgsql":
|
case "pgsql":
|
||||||
if connString == "" {
|
if connString == "" {
|
||||||
return nil, fmt.Errorf("%s: connection string is required for PostgreSQL format", label)
|
return nil, fmt.Errorf("%s: connection string is required for PostgreSQL format", label)
|
||||||
}
|
}
|
||||||
reader = pgsql.NewReader(&readers.ReaderOptions{ConnectionString: connString})
|
reader = pgsql.NewReader(newReaderOptions("", connString))
|
||||||
case "sqlite", "sqlite3":
|
case "sqlite", "sqlite3":
|
||||||
// SQLite can use either file path or connection string
|
// SQLite can use either file path or connection string
|
||||||
dbPath := filePath
|
dbPath := filePath
|
||||||
@@ -305,7 +305,7 @@ func readDatabaseForEdit(dbType, filePath, connString, label string) (*models.Da
|
|||||||
if dbPath == "" {
|
if dbPath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path or connection string is required for SQLite format", label)
|
return nil, fmt.Errorf("%s: file path or connection string is required for SQLite format", label)
|
||||||
}
|
}
|
||||||
reader = sqlite.NewReader(&readers.ReaderOptions{FilePath: dbPath})
|
reader = sqlite.NewReader(newReaderOptions(dbPath, ""))
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%s: unsupported format: %s", label, dbType)
|
return nil, fmt.Errorf("%s: unsupported format: %s", label, dbType)
|
||||||
}
|
}
|
||||||
@@ -323,31 +323,31 @@ func writeDatabaseForEdit(dbType, filePath, connString string, db *models.Databa
|
|||||||
|
|
||||||
switch strings.ToLower(dbType) {
|
switch strings.ToLower(dbType) {
|
||||||
case "dbml":
|
case "dbml":
|
||||||
writer = wdbml.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wdbml.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "dctx":
|
case "dctx":
|
||||||
writer = wdctx.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wdctx.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "drawdb":
|
case "drawdb":
|
||||||
writer = wdrawdb.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wdrawdb.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "graphql":
|
case "graphql":
|
||||||
writer = wgraphql.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wgraphql.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "json":
|
case "json":
|
||||||
writer = wjson.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wjson.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "yaml":
|
case "yaml":
|
||||||
writer = wyaml.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wyaml.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "gorm":
|
case "gorm":
|
||||||
writer = wgorm.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wgorm.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "bun":
|
case "bun":
|
||||||
writer = wbun.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wbun.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "drizzle":
|
case "drizzle":
|
||||||
writer = wdrizzle.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wdrizzle.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "prisma":
|
case "prisma":
|
||||||
writer = wprisma.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wprisma.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "typeorm":
|
case "typeorm":
|
||||||
writer = wtypeorm.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wtypeorm.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "sqlite", "sqlite3":
|
case "sqlite", "sqlite3":
|
||||||
writer = wsqlite.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wsqlite.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
case "pgsql":
|
case "pgsql":
|
||||||
writer = wpgsql.NewWriter(&writers.WriterOptions{OutputPath: filePath})
|
writer = wpgsql.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%s: unsupported format: %s", label, dbType)
|
return fmt.Errorf("%s: unsupported format: %s", label, dbType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,73 +221,73 @@ func readDatabaseForInspect(dbType, filePath, connString string) (*models.Databa
|
|||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for DBML format")
|
return nil, fmt.Errorf("file path is required for DBML format")
|
||||||
}
|
}
|
||||||
reader = dbml.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = dbml.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "dctx":
|
case "dctx":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for DCTX format")
|
return nil, fmt.Errorf("file path is required for DCTX format")
|
||||||
}
|
}
|
||||||
reader = dctx.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = dctx.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "drawdb":
|
case "drawdb":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for DrawDB format")
|
return nil, fmt.Errorf("file path is required for DrawDB format")
|
||||||
}
|
}
|
||||||
reader = drawdb.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = drawdb.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "graphql":
|
case "graphql":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for GraphQL format")
|
return nil, fmt.Errorf("file path is required for GraphQL format")
|
||||||
}
|
}
|
||||||
reader = graphql.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = graphql.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "json":
|
case "json":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for JSON format")
|
return nil, fmt.Errorf("file path is required for JSON format")
|
||||||
}
|
}
|
||||||
reader = json.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = json.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "yaml", "yml":
|
case "yaml", "yml":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for YAML format")
|
return nil, fmt.Errorf("file path is required for YAML format")
|
||||||
}
|
}
|
||||||
reader = yaml.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = yaml.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "gorm":
|
case "gorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for GORM format")
|
return nil, fmt.Errorf("file path is required for GORM format")
|
||||||
}
|
}
|
||||||
reader = gorm.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = gorm.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "bun":
|
case "bun":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for Bun format")
|
return nil, fmt.Errorf("file path is required for Bun format")
|
||||||
}
|
}
|
||||||
reader = bun.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = bun.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "drizzle":
|
case "drizzle":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for Drizzle format")
|
return nil, fmt.Errorf("file path is required for Drizzle format")
|
||||||
}
|
}
|
||||||
reader = drizzle.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = drizzle.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "prisma":
|
case "prisma":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for Prisma format")
|
return nil, fmt.Errorf("file path is required for Prisma format")
|
||||||
}
|
}
|
||||||
reader = prisma.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = prisma.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "typeorm":
|
case "typeorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("file path is required for TypeORM format")
|
return nil, fmt.Errorf("file path is required for TypeORM format")
|
||||||
}
|
}
|
||||||
reader = typeorm.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = typeorm.NewReader(newReaderOptions(filePath, ""))
|
||||||
|
|
||||||
case "pgsql", "postgres", "postgresql":
|
case "pgsql", "postgres", "postgresql":
|
||||||
if connString == "" {
|
if connString == "" {
|
||||||
return nil, fmt.Errorf("connection string is required for PostgreSQL format")
|
return nil, fmt.Errorf("connection string is required for PostgreSQL format")
|
||||||
}
|
}
|
||||||
reader = pgsql.NewReader(&readers.ReaderOptions{ConnectionString: connString})
|
reader = pgsql.NewReader(newReaderOptions("", connString))
|
||||||
|
|
||||||
case "sqlite", "sqlite3":
|
case "sqlite", "sqlite3":
|
||||||
// SQLite can use either file path or connection string
|
// SQLite can use either file path or connection string
|
||||||
@@ -298,7 +298,7 @@ func readDatabaseForInspect(dbType, filePath, connString string) (*models.Databa
|
|||||||
if dbPath == "" {
|
if dbPath == "" {
|
||||||
return nil, fmt.Errorf("file path or connection string is required for SQLite format")
|
return nil, fmt.Errorf("file path or connection string is required for SQLite format")
|
||||||
}
|
}
|
||||||
reader = sqlite.NewReader(&readers.ReaderOptions{FilePath: dbPath})
|
reader = sqlite.NewReader(newReaderOptions(dbPath, ""))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported database type: %s", dbType)
|
return nil, fmt.Errorf("unsupported database type: %s", dbType)
|
||||||
|
|||||||
@@ -284,62 +284,62 @@ func readDatabaseForMerge(dbType, filePath, connString, label string) (*models.D
|
|||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for DBML format", label)
|
return nil, fmt.Errorf("%s: file path is required for DBML format", label)
|
||||||
}
|
}
|
||||||
reader = dbml.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = dbml.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "dctx":
|
case "dctx":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for DCTX format", label)
|
return nil, fmt.Errorf("%s: file path is required for DCTX format", label)
|
||||||
}
|
}
|
||||||
reader = dctx.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = dctx.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "drawdb":
|
case "drawdb":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for DrawDB format", label)
|
return nil, fmt.Errorf("%s: file path is required for DrawDB format", label)
|
||||||
}
|
}
|
||||||
reader = drawdb.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = drawdb.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "graphql":
|
case "graphql":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for GraphQL format", label)
|
return nil, fmt.Errorf("%s: file path is required for GraphQL format", label)
|
||||||
}
|
}
|
||||||
reader = graphql.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = graphql.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "json":
|
case "json":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for JSON format", label)
|
return nil, fmt.Errorf("%s: file path is required for JSON format", label)
|
||||||
}
|
}
|
||||||
reader = json.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = json.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "yaml":
|
case "yaml":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for YAML format", label)
|
return nil, fmt.Errorf("%s: file path is required for YAML format", label)
|
||||||
}
|
}
|
||||||
reader = yaml.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = yaml.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "gorm":
|
case "gorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for GORM format", label)
|
return nil, fmt.Errorf("%s: file path is required for GORM format", label)
|
||||||
}
|
}
|
||||||
reader = gorm.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = gorm.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "bun":
|
case "bun":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for Bun format", label)
|
return nil, fmt.Errorf("%s: file path is required for Bun format", label)
|
||||||
}
|
}
|
||||||
reader = bun.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = bun.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "drizzle":
|
case "drizzle":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for Drizzle format", label)
|
return nil, fmt.Errorf("%s: file path is required for Drizzle format", label)
|
||||||
}
|
}
|
||||||
reader = drizzle.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = drizzle.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "prisma":
|
case "prisma":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for Prisma format", label)
|
return nil, fmt.Errorf("%s: file path is required for Prisma format", label)
|
||||||
}
|
}
|
||||||
reader = prisma.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = prisma.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "typeorm":
|
case "typeorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path is required for TypeORM format", label)
|
return nil, fmt.Errorf("%s: file path is required for TypeORM format", label)
|
||||||
}
|
}
|
||||||
reader = typeorm.NewReader(&readers.ReaderOptions{FilePath: filePath})
|
reader = typeorm.NewReader(newReaderOptions(filePath, ""))
|
||||||
case "pgsql":
|
case "pgsql":
|
||||||
if connString == "" {
|
if connString == "" {
|
||||||
return nil, fmt.Errorf("%s: connection string is required for PostgreSQL format", label)
|
return nil, fmt.Errorf("%s: connection string is required for PostgreSQL format", label)
|
||||||
}
|
}
|
||||||
reader = pgsql.NewReader(&readers.ReaderOptions{ConnectionString: connString})
|
reader = pgsql.NewReader(newReaderOptions("", connString))
|
||||||
case "sqlite", "sqlite3":
|
case "sqlite", "sqlite3":
|
||||||
// SQLite can use either file path or connection string
|
// SQLite can use either file path or connection string
|
||||||
dbPath := filePath
|
dbPath := filePath
|
||||||
@@ -349,7 +349,7 @@ func readDatabaseForMerge(dbType, filePath, connString, label string) (*models.D
|
|||||||
if dbPath == "" {
|
if dbPath == "" {
|
||||||
return nil, fmt.Errorf("%s: file path or connection string is required for SQLite format", label)
|
return nil, fmt.Errorf("%s: file path or connection string is required for SQLite format", label)
|
||||||
}
|
}
|
||||||
reader = sqlite.NewReader(&readers.ReaderOptions{FilePath: dbPath})
|
reader = sqlite.NewReader(newReaderOptions(dbPath, ""))
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%s: unsupported format '%s'", label, dbType)
|
return nil, fmt.Errorf("%s: unsupported format '%s'", label, dbType)
|
||||||
}
|
}
|
||||||
@@ -370,61 +370,61 @@ func writeDatabaseForMerge(dbType, filePath, connString string, db *models.Datab
|
|||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for DBML format", label)
|
return fmt.Errorf("%s: file path is required for DBML format", label)
|
||||||
}
|
}
|
||||||
writer = wdbml.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wdbml.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "dctx":
|
case "dctx":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for DCTX format", label)
|
return fmt.Errorf("%s: file path is required for DCTX format", label)
|
||||||
}
|
}
|
||||||
writer = wdctx.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wdctx.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "drawdb":
|
case "drawdb":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for DrawDB format", label)
|
return fmt.Errorf("%s: file path is required for DrawDB format", label)
|
||||||
}
|
}
|
||||||
writer = wdrawdb.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wdrawdb.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "graphql":
|
case "graphql":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for GraphQL format", label)
|
return fmt.Errorf("%s: file path is required for GraphQL format", label)
|
||||||
}
|
}
|
||||||
writer = wgraphql.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wgraphql.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "json":
|
case "json":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for JSON format", label)
|
return fmt.Errorf("%s: file path is required for JSON format", label)
|
||||||
}
|
}
|
||||||
writer = wjson.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wjson.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "yaml":
|
case "yaml":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for YAML format", label)
|
return fmt.Errorf("%s: file path is required for YAML format", label)
|
||||||
}
|
}
|
||||||
writer = wyaml.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wyaml.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "gorm":
|
case "gorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for GORM format", label)
|
return fmt.Errorf("%s: file path is required for GORM format", label)
|
||||||
}
|
}
|
||||||
writer = wgorm.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wgorm.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "bun":
|
case "bun":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for Bun format", label)
|
return fmt.Errorf("%s: file path is required for Bun format", label)
|
||||||
}
|
}
|
||||||
writer = wbun.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wbun.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "drizzle":
|
case "drizzle":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for Drizzle format", label)
|
return fmt.Errorf("%s: file path is required for Drizzle format", label)
|
||||||
}
|
}
|
||||||
writer = wdrizzle.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wdrizzle.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "prisma":
|
case "prisma":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for Prisma format", label)
|
return fmt.Errorf("%s: file path is required for Prisma format", label)
|
||||||
}
|
}
|
||||||
writer = wprisma.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wprisma.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "typeorm":
|
case "typeorm":
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("%s: file path is required for TypeORM format", label)
|
return fmt.Errorf("%s: file path is required for TypeORM format", label)
|
||||||
}
|
}
|
||||||
writer = wtypeorm.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wtypeorm.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "sqlite", "sqlite3":
|
case "sqlite", "sqlite3":
|
||||||
writer = wsqlite.NewWriter(&writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema})
|
writer = wsqlite.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||||
case "pgsql":
|
case "pgsql":
|
||||||
writerOpts := &writers.WriterOptions{OutputPath: filePath, FlattenSchema: flattenSchema}
|
writerOpts := newWriterOptions(filePath, "", flattenSchema, "")
|
||||||
if connString != "" {
|
if connString != "" {
|
||||||
writerOpts.Metadata = map[string]interface{}{
|
writerOpts.Metadata = map[string]interface{}{
|
||||||
"connection_string": connString,
|
"connection_string": connString,
|
||||||
|
|||||||
24
cmd/relspec/prisma_options.go
Normal file
24
cmd/relspec/prisma_options.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.warky.dev/wdevs/relspecgo/pkg/readers"
|
||||||
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newReaderOptions(filePath, connString string) *readers.ReaderOptions {
|
||||||
|
return &readers.ReaderOptions{
|
||||||
|
FilePath: filePath,
|
||||||
|
ConnectionString: connString,
|
||||||
|
Prisma7: prisma7,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWriterOptions(outputPath, packageName string, flattenSchema bool, nullableTypes string) *writers.WriterOptions {
|
||||||
|
return &writers.WriterOptions{
|
||||||
|
OutputPath: outputPath,
|
||||||
|
PackageName: packageName,
|
||||||
|
FlattenSchema: flattenSchema,
|
||||||
|
NullableTypes: nullableTypes,
|
||||||
|
Prisma7: prisma7,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ var (
|
|||||||
// Version information, set via ldflags during build
|
// Version information, set via ldflags during build
|
||||||
version = "dev"
|
version = "dev"
|
||||||
buildDate = "unknown"
|
buildDate = "unknown"
|
||||||
|
prisma7 bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -68,4 +69,5 @@ func init() {
|
|||||||
rootCmd.AddCommand(mergeCmd)
|
rootCmd.AddCommand(mergeCmd)
|
||||||
rootCmd.AddCommand(splitCmd)
|
rootCmd.AddCommand(splitCmd)
|
||||||
rootCmd.AddCommand(versionCmd)
|
rootCmd.AddCommand(versionCmd)
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&prisma7, "prisma7", false, "Use Prisma 7 generator conventions when reading/writing Prisma schemas")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: Hein (Warky Devs) <hein@warky.dev>
|
# Maintainer: Hein (Warky Devs) <hein@warky.dev>
|
||||||
pkgname=relspec
|
pkgname=relspec
|
||||||
pkgver=1.0.48
|
pkgver=1.0.50
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs."
|
pkgdesc="RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs."
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: relspec
|
Name: relspec
|
||||||
Version: 1.0.48
|
Version: 1.0.50
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs.
|
Summary: RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs.
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func (r *Reader) ReadTable() (*models.Table, error) {
|
|||||||
// parsePrisma parses Prisma schema content and returns a Database model
|
// parsePrisma parses Prisma schema content and returns a Database model
|
||||||
func (r *Reader) parsePrisma(content string) (*models.Database, error) {
|
func (r *Reader) parsePrisma(content string) (*models.Database, error) {
|
||||||
db := models.InitDatabase("database")
|
db := models.InitDatabase("database")
|
||||||
|
db.SourceFormat = "prisma"
|
||||||
|
|
||||||
if r.options.Metadata != nil {
|
if r.options.Metadata != nil {
|
||||||
if name, ok := r.options.Metadata["name"].(string); ok {
|
if name, ok := r.options.Metadata["name"].(string); ok {
|
||||||
@@ -139,7 +140,7 @@ func (r *Reader) parsePrisma(content string) (*models.Database, error) {
|
|||||||
case "datasource":
|
case "datasource":
|
||||||
r.parseDatasource(blockContent, db)
|
r.parseDatasource(blockContent, db)
|
||||||
case "generator":
|
case "generator":
|
||||||
// We don't need to do anything with generator blocks
|
r.parseGenerator(blockContent, db)
|
||||||
case "model":
|
case "model":
|
||||||
if currentTable != nil {
|
if currentTable != nil {
|
||||||
r.parseModelFields(blockContent, currentTable)
|
r.parseModelFields(blockContent, currentTable)
|
||||||
@@ -173,10 +174,34 @@ func (r *Reader) parsePrisma(content string) (*models.Database, error) {
|
|||||||
// Second pass: resolve relationships
|
// Second pass: resolve relationships
|
||||||
r.resolveRelationships(schema)
|
r.resolveRelationships(schema)
|
||||||
|
|
||||||
|
if db.SourceFormat == "prisma" && r.options != nil && r.options.Prisma7 {
|
||||||
|
db.SourceFormat = "prisma7"
|
||||||
|
}
|
||||||
|
|
||||||
db.Schemas = append(db.Schemas, schema)
|
db.Schemas = append(db.Schemas, schema)
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseGenerator(lines []string, db *models.Database) {
|
||||||
|
providerRegex := regexp.MustCompile(`provider\s*=\s*"([^"]+)"`)
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
if matches := providerRegex.FindStringSubmatch(line); matches != nil {
|
||||||
|
switch matches[1] {
|
||||||
|
case "prisma-client":
|
||||||
|
db.SourceFormat = "prisma7"
|
||||||
|
default:
|
||||||
|
db.SourceFormat = "prisma"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.options != nil && r.options.Prisma7 {
|
||||||
|
db.SourceFormat = "prisma7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parseDatasource extracts database type from datasource block
|
// parseDatasource extracts database type from datasource block
|
||||||
func (r *Reader) parseDatasource(lines []string, db *models.Database) {
|
func (r *Reader) parseDatasource(lines []string, db *models.Database) {
|
||||||
providerRegex := regexp.MustCompile(`provider\s*=\s*"?(\w+)"?`)
|
providerRegex := regexp.MustCompile(`provider\s*=\s*"?(\w+)"?`)
|
||||||
|
|||||||
77
pkg/readers/prisma/reader_test.go
Normal file
77
pkg/readers/prisma/reader_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package prisma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.warky.dev/wdevs/relspecgo/pkg/readers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadDatabase_Prisma7GeneratorSetsSourceFormat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
schemaPath := filepath.Join(tmpDir, "schema.prisma")
|
||||||
|
|
||||||
|
content := `datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client"
|
||||||
|
output = "./generated"
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
}`
|
||||||
|
|
||||||
|
if err := os.WriteFile(schemaPath, []byte(content), 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write schema: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := NewReader(&readers.ReaderOptions{FilePath: schemaPath})
|
||||||
|
db, err := reader.ReadDatabase()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadDatabase() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if db.SourceFormat != "prisma7" {
|
||||||
|
t.Fatalf("expected SourceFormat prisma7, got %q", db.SourceFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadDatabase_Prisma7FlagSetsSourceFormatWithoutGenerator(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
schemaPath := filepath.Join(tmpDir, "schema.prisma")
|
||||||
|
|
||||||
|
content := `datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
}`
|
||||||
|
|
||||||
|
if err := os.WriteFile(schemaPath, []byte(content), 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write schema: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := NewReader(&readers.ReaderOptions{
|
||||||
|
FilePath: schemaPath,
|
||||||
|
Prisma7: true,
|
||||||
|
})
|
||||||
|
db, err := reader.ReadDatabase()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadDatabase() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if db.SourceFormat != "prisma7" {
|
||||||
|
t.Fatalf("expected SourceFormat prisma7 from flag, got %q", db.SourceFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,9 @@ type ReaderOptions struct {
|
|||||||
// ConnectionString is the database connection string (for DB readers)
|
// ConnectionString is the database connection string (for DB readers)
|
||||||
ConnectionString string
|
ConnectionString string
|
||||||
|
|
||||||
|
// Prisma7 enables Prisma 7-specific handling for Prisma schemas.
|
||||||
|
Prisma7 bool
|
||||||
|
|
||||||
// Additional options can be added here as needed
|
// Additional options can be added here as needed
|
||||||
Metadata map[string]interface{}
|
Metadata map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -329,7 +329,11 @@ func (w *MigrationWriter) generateAlterTableScripts(schema *models.Schema, model
|
|||||||
// Column doesn't exist, add it
|
// Column doesn't exist, add it
|
||||||
defaultVal := ""
|
defaultVal := ""
|
||||||
if modelCol.Default != nil {
|
if modelCol.Default != nil {
|
||||||
defaultVal = fmt.Sprintf("%v", modelCol.Default)
|
if value, ok := modelCol.Default.(string); ok {
|
||||||
|
defaultVal = writers.QuoteDefaultValue(value, modelCol.Type)
|
||||||
|
} else {
|
||||||
|
defaultVal = fmt.Sprintf("%v", modelCol.Default)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sql, err := w.executor.ExecuteAddColumn(AddColumnData{
|
sql, err := w.executor.ExecuteAddColumn(AddColumnData{
|
||||||
@@ -382,7 +386,11 @@ func (w *MigrationWriter) generateAlterTableScripts(schema *models.Schema, model
|
|||||||
setDefault := modelCol.Default != nil
|
setDefault := modelCol.Default != nil
|
||||||
defaultVal := ""
|
defaultVal := ""
|
||||||
if setDefault {
|
if setDefault {
|
||||||
defaultVal = fmt.Sprintf("%v", modelCol.Default)
|
if value, ok := modelCol.Default.(string); ok {
|
||||||
|
defaultVal = writers.QuoteDefaultValue(value, modelCol.Type)
|
||||||
|
} else {
|
||||||
|
defaultVal = fmt.Sprintf("%v", modelCol.Default)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sql, err := w.executor.ExecuteAlterColumnDefault(AlterColumnDefaultData{
|
sql, err := w.executor.ExecuteAlterColumnDefault(AlterColumnDefaultData{
|
||||||
|
|||||||
@@ -57,6 +57,46 @@ func TestWriteMigration_NewTable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteMigration_ArrayDefault(t *testing.T) {
|
||||||
|
current := models.InitDatabase("testdb")
|
||||||
|
currentSchema := models.InitSchema("public")
|
||||||
|
current.Schemas = append(current.Schemas, currentSchema)
|
||||||
|
|
||||||
|
model := models.InitDatabase("testdb")
|
||||||
|
modelSchema := models.InitSchema("public")
|
||||||
|
|
||||||
|
table := models.InitTable("plans", "public")
|
||||||
|
|
||||||
|
tagsCol := models.InitColumn("tags", "plans", "public")
|
||||||
|
tagsCol.Type = "text[]"
|
||||||
|
tagsCol.NotNull = true
|
||||||
|
tagsCol.Default = "''{}''"
|
||||||
|
table.Columns["tags"] = tagsCol
|
||||||
|
|
||||||
|
modelSchema.Tables = append(modelSchema.Tables, table)
|
||||||
|
model.Schemas = append(model.Schemas, modelSchema)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create writer: %v", err)
|
||||||
|
}
|
||||||
|
writer.writer = &buf
|
||||||
|
|
||||||
|
err = writer.WriteMigration(model, current)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("WriteMigration failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
if !strings.Contains(output, "tags text[] DEFAULT '{}' NOT NULL") {
|
||||||
|
t.Fatalf("expected normalized array default in migration, got:\n%s", output)
|
||||||
|
}
|
||||||
|
if strings.Contains(output, "'''{}'''") {
|
||||||
|
t.Fatalf("migration still contains triple-quoted array default:\n%s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriteMigration_WithAudit(t *testing.T) {
|
func TestWriteMigration_WithAudit(t *testing.T) {
|
||||||
// Current database (empty)
|
// Current database (empty)
|
||||||
current := models.InitDatabase("testdb")
|
current := models.InitDatabase("testdb")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||||
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed templates/*.tmpl
|
//go:embed templates/*.tmpl
|
||||||
@@ -495,7 +496,11 @@ func BuildCreateTableData(schemaName string, table *models.Table) CreateTableDat
|
|||||||
NotNull: col.NotNull,
|
NotNull: col.NotNull,
|
||||||
}
|
}
|
||||||
if col.Default != nil {
|
if col.Default != nil {
|
||||||
colData.Default = fmt.Sprintf("%v", col.Default)
|
if value, ok := col.Default.(string); ok {
|
||||||
|
colData.Default = writers.QuoteDefaultValue(value, col.Type)
|
||||||
|
} else {
|
||||||
|
colData.Default = fmt.Sprintf("%v", col.Default)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
columns = append(columns, colData)
|
columns = append(columns, colData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -523,15 +523,7 @@ 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:
|
||||||
// Strip backticks - DBML uses them for SQL expressions but PostgreSQL doesn't
|
parts = append(parts, fmt.Sprintf("DEFAULT %s", writers.QuoteDefaultValue(stripBackticks(v), col.Type)))
|
||||||
cleanDefault := stripBackticks(v)
|
|
||||||
if strings.HasPrefix(cleanDefault, "nextval") || strings.HasPrefix(cleanDefault, "CURRENT_") || strings.Contains(cleanDefault, "()") {
|
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", cleanDefault))
|
|
||||||
} else if cleanDefault == "true" || cleanDefault == "false" {
|
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", cleanDefault))
|
|
||||||
} else {
|
|
||||||
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))
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (w *Writer) databaseToPrisma(db *models.Database) string {
|
|||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
|
|
||||||
// Write generator block
|
// Write generator block
|
||||||
sb.WriteString(w.generateGenerator())
|
sb.WriteString(w.generateGenerator(db))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
|
|
||||||
// Process all schemas (typically just one in Prisma)
|
// Process all schemas (typically just one in Prisma)
|
||||||
@@ -114,13 +114,28 @@ func (w *Writer) generateDatasource(db *models.Database) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generateGenerator generates the generator block
|
// generateGenerator generates the generator block
|
||||||
func (w *Writer) generateGenerator() string {
|
func (w *Writer) generateGenerator(db *models.Database) string {
|
||||||
|
if w.usePrisma7Generator(db) {
|
||||||
|
return `generator client {
|
||||||
|
provider = "prisma-client"
|
||||||
|
output = "./generated"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
return `generator client {
|
return `generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Writer) usePrisma7Generator(db *models.Database) bool {
|
||||||
|
if w.options != nil && w.options.Prisma7 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return db != nil && db.SourceFormat == "prisma7"
|
||||||
|
}
|
||||||
|
|
||||||
// enumToPrisma converts an Enum to Prisma enum block
|
// enumToPrisma converts an Enum to Prisma enum block
|
||||||
func (w *Writer) enumToPrisma(enum *models.Enum) string {
|
func (w *Writer) enumToPrisma(enum *models.Enum) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|||||||
52
pkg/writers/prisma/writer_test.go
Normal file
52
pkg/writers/prisma/writer_test.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package prisma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||||
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateGenerator_DefaultsToPrismaClientJS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
writer := NewWriter(&writers.WriterOptions{})
|
||||||
|
db := models.InitDatabase("testdb")
|
||||||
|
|
||||||
|
got := writer.generateGenerator(db)
|
||||||
|
if !strings.Contains(got, `provider = "prisma-client-js"`) {
|
||||||
|
t.Fatalf("expected prisma-client-js generator, got:\n%s", got)
|
||||||
|
}
|
||||||
|
if strings.Contains(got, `output = "./generated"`) {
|
||||||
|
t.Fatalf("did not expect prisma7 output path in default generator:\n%s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateGenerator_Prisma7FlagUsesPrismaClient(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
writer := NewWriter(&writers.WriterOptions{Prisma7: true})
|
||||||
|
db := models.InitDatabase("testdb")
|
||||||
|
|
||||||
|
got := writer.generateGenerator(db)
|
||||||
|
if !strings.Contains(got, `provider = "prisma-client"`) {
|
||||||
|
t.Fatalf("expected prisma-client generator, got:\n%s", got)
|
||||||
|
}
|
||||||
|
if !strings.Contains(got, `output = "./generated"`) {
|
||||||
|
t.Fatalf("expected prisma7 output path, got:\n%s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateGenerator_Prisma7SourceFormatUsesPrismaClient(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
writer := NewWriter(&writers.WriterOptions{})
|
||||||
|
db := models.InitDatabase("testdb")
|
||||||
|
db.SourceFormat = "prisma7"
|
||||||
|
|
||||||
|
got := writer.generateGenerator(db)
|
||||||
|
if !strings.Contains(got, `provider = "prisma-client"`) {
|
||||||
|
t.Fatalf("expected prisma-client generator from source format, got:\n%s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,9 @@ type WriterOptions struct {
|
|||||||
// "stdlib" — database/sql (sql.NullString, sql.NullInt32, …)
|
// "stdlib" — database/sql (sql.NullString, sql.NullInt32, …)
|
||||||
NullableTypes string
|
NullableTypes string
|
||||||
|
|
||||||
|
// Prisma7 enables Prisma 7-specific output for Prisma writers.
|
||||||
|
Prisma7 bool
|
||||||
|
|
||||||
// Additional options can be added here as needed
|
// Additional options can be added here as needed
|
||||||
Metadata map[string]interface{}
|
Metadata map[string]interface{}
|
||||||
}
|
}
|
||||||
@@ -110,8 +113,12 @@ func SanitizeFilename(name string) string {
|
|||||||
// Examples (bigint): "0" → "0"
|
// Examples (bigint): "0" → "0"
|
||||||
// Examples (timestamp): "now()" → "now()" (function call – never quoted)
|
// Examples (timestamp): "now()" → "now()" (function call – never quoted)
|
||||||
func QuoteDefaultValue(value, sqlType string) string {
|
func QuoteDefaultValue(value, sqlType string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
|
||||||
// Function calls are never quoted regardless of column type.
|
// Function calls are never quoted regardless of column type.
|
||||||
if strings.Contains(value, "(") || strings.Contains(value, ")") {
|
if strings.Contains(value, "(") || strings.Contains(value, ")") ||
|
||||||
|
strings.Contains(value, "::") ||
|
||||||
|
strings.HasPrefix(strings.ToUpper(value), "ARRAY[") {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +128,16 @@ func QuoteDefaultValue(value, sqlType string) string {
|
|||||||
baseType = baseType[:idx]
|
baseType = baseType[:idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isArraySQLType(baseType) {
|
||||||
|
if arrayLiteral, ok := normalizeArrayDefaultLiteral(value); ok {
|
||||||
|
return quoteSQLLiteral(arrayLiteral)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isQuotedSQLLiteral(value) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
// Types whose default values must NOT be quoted.
|
// Types whose default values must NOT be quoted.
|
||||||
unquotedTypes := map[string]bool{
|
unquotedTypes := map[string]bool{
|
||||||
// Integer types
|
// Integer types
|
||||||
@@ -154,7 +171,32 @@ func QuoteDefaultValue(value, sqlType string) string {
|
|||||||
|
|
||||||
// Everything else (text, varchar, char, uuid, date, time, timestamp, json, …)
|
// Everything else (text, varchar, char, uuid, date, time, timestamp, json, …)
|
||||||
// is treated as a quoted literal.
|
// is treated as a quoted literal.
|
||||||
return "'" + value + "'"
|
return quoteSQLLiteral(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isArraySQLType(sqlType string) bool {
|
||||||
|
return strings.HasSuffix(sqlType, "[]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeArrayDefaultLiteral(value string) (string, bool) {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(value, "''{") && strings.HasSuffix(value, "}''"):
|
||||||
|
return value[2 : len(value)-2], true
|
||||||
|
case strings.HasPrefix(value, "'{") && strings.HasSuffix(value, "}'"):
|
||||||
|
return value[1 : len(value)-1], true
|
||||||
|
case strings.HasPrefix(value, "{") && strings.HasSuffix(value, "}"):
|
||||||
|
return value, true
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isQuotedSQLLiteral(value string) bool {
|
||||||
|
return len(value) >= 2 && strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func quoteSQLLiteral(value string) string {
|
||||||
|
return "'" + strings.ReplaceAll(value, "'", "''") + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizeStructTagValue sanitizes a value to be safely used inside Go struct tags.
|
// SanitizeStructTagValue sanitizes a value to be safely used inside Go struct tags.
|
||||||
|
|||||||
54
pkg/writers/writer_test.go
Normal file
54
pkg/writers/writer_test.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package writers
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestQuoteDefaultValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
sqlType string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "text default is quoted",
|
||||||
|
value: "active",
|
||||||
|
sqlType: "text",
|
||||||
|
want: "'active'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array default from bare literal is quoted once",
|
||||||
|
value: "{}",
|
||||||
|
sqlType: "text[]",
|
||||||
|
want: "'{}'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array default from quoted literal is preserved",
|
||||||
|
value: "'{}'",
|
||||||
|
sqlType: "text[]",
|
||||||
|
want: "'{}'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array default from double quoted literal is normalized",
|
||||||
|
value: "''{}''",
|
||||||
|
sqlType: "text[]",
|
||||||
|
want: "'{}'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function default is left alone",
|
||||||
|
value: "now()",
|
||||||
|
sqlType: "timestamptz",
|
||||||
|
want: "now()",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := QuoteDefaultValue(tt.value, tt.sqlType)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("QuoteDefaultValue(%q, %q) = %q, want %q", tt.value, tt.sqlType, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user