fix: contain generated files and assets and harden SQL rendering
This commit is contained in:
+186
-123
@@ -37,6 +37,9 @@ type ExecutionError = writers.ExecutionError
|
||||
|
||||
// NewWriter creates a new PostgreSQL SQL writer
|
||||
func NewWriter(options *writers.WriterOptions) *Writer {
|
||||
if options == nil {
|
||||
options = &writers.WriterOptions{}
|
||||
}
|
||||
executor, _ := NewTemplateExecutor(options.FlattenSchema)
|
||||
return &Writer{
|
||||
options: options,
|
||||
@@ -46,11 +49,22 @@ func NewWriter(options *writers.WriterOptions) *Writer {
|
||||
|
||||
// qualTable returns a schema-qualified name using the writer's FlattenSchema setting.
|
||||
func (w *Writer) qualTable(schema, name string) string {
|
||||
return writers.QualifiedTableName(schema, name, w.options.FlattenSchema)
|
||||
if schema == "" {
|
||||
return quoteIdent(name)
|
||||
}
|
||||
if w.options.FlattenSchema {
|
||||
return quoteIdent(schema + "_" + name)
|
||||
}
|
||||
return quoteIdent(schema) + "." + quoteIdent(name)
|
||||
}
|
||||
|
||||
// WriteDatabase writes the entire database schema as SQL
|
||||
func (w *Writer) WriteDatabase(db *models.Database) error {
|
||||
var preparationErr error
|
||||
db, preparationErr = prepareModel(db, w.options.LowercaseIdentifiers)
|
||||
if preparationErr != nil {
|
||||
return preparationErr
|
||||
}
|
||||
// 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)
|
||||
@@ -79,7 +93,7 @@ func (w *Writer) WriteDatabase(db *models.Database) error {
|
||||
|
||||
// Write header comment
|
||||
fmt.Fprintf(w.writer, "-- PostgreSQL Database Schema\n")
|
||||
fmt.Fprintf(w.writer, "-- Database: %s\n", db.Name)
|
||||
fmt.Fprintf(w.writer, "-- Database: %s\n", commentText(db.Name))
|
||||
fmt.Fprintf(w.writer, "-- Generated by RelSpec\n")
|
||||
if w.options.ContinueOnError {
|
||||
fmt.Fprintf(w.writer, "\\set ON_ERROR_STOP off\n")
|
||||
@@ -110,11 +124,16 @@ func (w *Writer) WriteDatabase(db *models.Database) error {
|
||||
// GenerateDatabaseStatements generates SQL statements as a list for the entire database
|
||||
// Returns a slice of SQL statements that can be executed independently
|
||||
func (w *Writer) GenerateDatabaseStatements(db *models.Database) ([]string, error) {
|
||||
var preparationErr error
|
||||
db, preparationErr = prepareModel(db, w.options.LowercaseIdentifiers)
|
||||
if preparationErr != nil {
|
||||
return nil, preparationErr
|
||||
}
|
||||
statements := []string{}
|
||||
|
||||
// Add header comment
|
||||
statements = append(statements, "-- PostgreSQL Database Schema")
|
||||
statements = append(statements, fmt.Sprintf("-- Database: %s", db.Name))
|
||||
statements = append(statements, fmt.Sprintf("-- Database: %s", commentText(db.Name)))
|
||||
statements = append(statements, "-- Generated by RelSpec")
|
||||
|
||||
for _, schema := range db.Schemas {
|
||||
@@ -161,7 +180,7 @@ func (w *Writer) primaryKeySequenceStatement(schema *models.Schema, table *model
|
||||
}
|
||||
|
||||
return fmt.Sprintf("CREATE SEQUENCE IF NOT EXISTS %s\n INCREMENT 1\n MINVALUE 1\n MAXVALUE 9223372036854775807\n START 1\n CACHE 1",
|
||||
w.qualTable(schema.SQLName(), seqName))
|
||||
w.qualTable(schema.Name, seqName))
|
||||
}
|
||||
|
||||
// primaryKeySetvalStatement returns the statement that moves a table's primary key
|
||||
@@ -182,6 +201,11 @@ func (w *Writer) primaryKeySetvalStatement(schema *models.Schema, table *models.
|
||||
|
||||
// GenerateSchemaStatements generates SQL statements as a list for a single schema
|
||||
func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, error) {
|
||||
var err error
|
||||
schema, err = w.prepareSchema(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
preflights, err := w.generateTypePreflights(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -199,8 +223,8 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
|
||||
// Phase 1: Create schema (skip entirely when flattening)
|
||||
if schema.Name != "public" && !w.options.FlattenSchema {
|
||||
statements = append(statements, fmt.Sprintf("-- Schema: %s", schema.Name))
|
||||
statements = append(statements, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", schema.SQLName()))
|
||||
statements = append(statements, fmt.Sprintf("-- Schema: %s", commentText(schema.Name)))
|
||||
statements = append(statements, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", quoteIdent(schema.Name)))
|
||||
}
|
||||
|
||||
for _, extension := range requiredExtensions(schema) {
|
||||
@@ -258,14 +282,14 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
pkCols := []string{}
|
||||
for _, col := range table.Columns {
|
||||
if col.IsPrimaryKey {
|
||||
pkCols = append(pkCols, col.SQLName())
|
||||
pkCols = append(pkCols, col.Name)
|
||||
}
|
||||
}
|
||||
if len(pkCols) > 0 {
|
||||
// Sort for consistent output
|
||||
sort.Strings(pkCols)
|
||||
pkColumns = pkCols
|
||||
pkName = fmt.Sprintf("pk_%s_%s", schema.SQLName(), table.SQLName())
|
||||
pkName = fmt.Sprintf("pk_%s_%s", schema.Name, table.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +306,7 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
TableName: table.Name,
|
||||
ConstraintName: pkName,
|
||||
AutoGenNames: formatStringList(autoGenPKNames),
|
||||
Columns: strings.Join(pkColumns, ", "),
|
||||
Columns: quotedIdentifiers(pkColumns),
|
||||
ColumnNames: formatStringList(pkColumns),
|
||||
}
|
||||
|
||||
@@ -322,7 +346,7 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
|
||||
tablespaceClause := ""
|
||||
if ts := pgIndexDirectiveTablespace(index); ts != "" {
|
||||
tablespaceClause = fmt.Sprintf(" TABLESPACE %s", ts)
|
||||
tablespaceClause = fmt.Sprintf(" TABLESPACE %s", quoteIdent(ts))
|
||||
}
|
||||
|
||||
whereClause := ""
|
||||
@@ -331,7 +355,7 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
}
|
||||
|
||||
stmt := fmt.Sprintf("CREATE %sINDEX IF NOT EXISTS %s ON %s USING %s (%s)%s%s%s",
|
||||
uniqueStr, quoteIdentifier(index.Name), w.qualTable(schema.SQLName(), table.SQLName()), indexType, strings.Join(columnExprs, ", "), withClause, tablespaceClause, whereClause)
|
||||
uniqueStr, quoteIdentifier(index.Name), w.qualTable(schema.Name, table.Name), indexType, strings.Join(columnExprs, ", "), withClause, tablespaceClause, whereClause)
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
}
|
||||
@@ -348,7 +372,7 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
SchemaName: schema.Name,
|
||||
TableName: table.Name,
|
||||
ConstraintName: constraint.Name,
|
||||
Columns: strings.Join(constraint.Columns, ", "),
|
||||
Columns: quotedIdentifiers(constraint.Columns),
|
||||
UsingIndex: adoptableUniqueIndex(table, constraint),
|
||||
}
|
||||
|
||||
@@ -410,10 +434,10 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
SchemaName: schema.Name,
|
||||
TableName: table.Name,
|
||||
ConstraintName: constraint.Name,
|
||||
SourceColumns: strings.Join(constraint.Columns, ", "),
|
||||
SourceColumns: quotedIdentifiers(constraint.Columns),
|
||||
TargetSchema: refSchema,
|
||||
TargetTable: constraint.ReferencedTable,
|
||||
TargetColumns: strings.Join(constraint.ReferencedColumns, ", "),
|
||||
TargetColumns: quotedIdentifiers(constraint.ReferencedColumns),
|
||||
OnDelete: onDelete,
|
||||
OnUpdate: onUpdate,
|
||||
Deferral: constraint.DeferralClause(),
|
||||
@@ -441,15 +465,15 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
// Phase 7: Comments
|
||||
for _, table := range schema.Tables {
|
||||
if table.Comment != "" {
|
||||
stmt := fmt.Sprintf("COMMENT ON TABLE %s IS '%s'",
|
||||
w.qualTable(schema.SQLName(), table.SQLName()), escapeQuote(table.Comment))
|
||||
stmt := fmt.Sprintf("COMMENT ON TABLE %s IS %s",
|
||||
w.qualTable(schema.Name, table.Name), quote(table.Comment))
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
|
||||
for _, column := range sortColumns(table.Columns) {
|
||||
if column.Comment != "" {
|
||||
stmt := fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'",
|
||||
w.qualTable(schema.SQLName(), table.SQLName()), column.SQLName(), escapeQuote(column.Comment))
|
||||
stmt := fmt.Sprintf("COMMENT ON COLUMN %s.%s IS %s",
|
||||
w.qualTable(schema.Name, table.Name), quoteIdent(column.Name), quote(column.Comment))
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
}
|
||||
@@ -461,9 +485,14 @@ func (w *Writer) generateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
// GenerateAddColumnStatements generates ALTER TABLE ADD COLUMN statements for existing tables
|
||||
// This is useful for schema evolution when new columns are added to existing tables
|
||||
func (w *Writer) GenerateAddColumnStatements(schema *models.Schema) ([]string, error) {
|
||||
var err error
|
||||
schema, err = w.prepareSchema(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statements := []string{}
|
||||
|
||||
statements = append(statements, fmt.Sprintf("-- Add missing columns for schema: %s", schema.Name))
|
||||
statements = append(statements, fmt.Sprintf("-- Add missing columns for schema: %s", commentText(schema.Name)))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort columns by sequence or name for consistent output
|
||||
@@ -501,9 +530,14 @@ func (w *Writer) GenerateAddColumnStatements(schema *models.Schema) ([]string, e
|
||||
}
|
||||
|
||||
func (w *Writer) GenerateAlterColumnTypeStatements(schema *models.Schema) ([]string, error) {
|
||||
var err error
|
||||
schema, err = w.prepareSchema(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statements := []string{}
|
||||
|
||||
statements = append(statements, fmt.Sprintf("-- Alter column types for schema: %s", schema.Name))
|
||||
statements = append(statements, fmt.Sprintf("-- Alter column types for schema: %s", commentText(schema.Name)))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
columns := getSortedColumns(table.Columns)
|
||||
@@ -533,9 +567,14 @@ func (w *Writer) GenerateAlterColumnTypeStatements(schema *models.Schema) ([]str
|
||||
// statements to bring existing columns' DEFAULT clause in line with the
|
||||
// model, safe to run against a database that already has the columns.
|
||||
func (w *Writer) GenerateAlterColumnDefaultStatements(schema *models.Schema) ([]string, error) {
|
||||
var err error
|
||||
schema, err = w.prepareSchema(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statements := []string{}
|
||||
|
||||
statements = append(statements, fmt.Sprintf("-- Alter column defaults for schema: %s", schema.Name))
|
||||
statements = append(statements, fmt.Sprintf("-- Alter column defaults for schema: %s", commentText(schema.Name)))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
columns := getSortedColumns(table.Columns)
|
||||
@@ -592,7 +631,7 @@ func formatColumnDefaultSQL(col *models.Column) (setDefault bool, defaultVal str
|
||||
return false, ""
|
||||
}
|
||||
if value, ok := col.Default.(string); ok {
|
||||
return true, writers.QuoteDefaultValue(stripBackticks(value), col.Type)
|
||||
return true, quoteDefaultValue(stripBackticks(value), col.Type)
|
||||
}
|
||||
return true, fmt.Sprintf("%v", col.Default)
|
||||
}
|
||||
@@ -601,9 +640,14 @@ func formatColumnDefaultSQL(col *models.Column) (setDefault bool, defaultVal str
|
||||
// statements to bring existing columns' NOT NULL state in line with the
|
||||
// model, safe to run against a database that already has the columns.
|
||||
func (w *Writer) GenerateAlterColumnNullabilityStatements(schema *models.Schema) ([]string, error) {
|
||||
var err error
|
||||
schema, err = w.prepareSchema(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statements := []string{}
|
||||
|
||||
statements = append(statements, fmt.Sprintf("-- Alter column nullability for schema: %s", schema.Name))
|
||||
statements = append(statements, fmt.Sprintf("-- Alter column nullability for schema: %s", commentText(schema.Name)))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
columns := getSortedColumns(table.Columns)
|
||||
@@ -626,10 +670,15 @@ func (w *Writer) GenerateAlterColumnNullabilityStatements(schema *models.Schema)
|
||||
|
||||
// GenerateAddColumnsForDatabase generates ALTER TABLE ADD COLUMN statements for the entire database
|
||||
func (w *Writer) GenerateAddColumnsForDatabase(db *models.Database) ([]string, error) {
|
||||
prepared, preparationErr := prepareModel(db, w.options.LowercaseIdentifiers)
|
||||
if preparationErr != nil {
|
||||
return nil, preparationErr
|
||||
}
|
||||
db = prepared
|
||||
statements := []string{}
|
||||
|
||||
statements = append(statements, "-- Add missing columns to existing tables")
|
||||
statements = append(statements, fmt.Sprintf("-- Database: %s", db.Name))
|
||||
statements = append(statements, fmt.Sprintf("-- Database: %s", commentText(db.Name)))
|
||||
statements = append(statements, "-- Generated by RelSpec")
|
||||
|
||||
for _, schema := range db.Schemas {
|
||||
@@ -666,7 +715,7 @@ func (w *Writer) generateCreateTableStatement(schema *models.Schema, table *mode
|
||||
}
|
||||
|
||||
stmt := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (\n%s\n)%s",
|
||||
w.qualTable(schema.SQLName(), table.SQLName()), strings.Join(columnDefs, ",\n"),
|
||||
w.qualTable(schema.Name, table.Name), strings.Join(columnDefs, ",\n"),
|
||||
pgTableDirectiveSuffix(table))
|
||||
statements = append(statements, stmt)
|
||||
|
||||
@@ -675,7 +724,7 @@ func (w *Writer) generateCreateTableStatement(schema *models.Schema, table *mode
|
||||
|
||||
// generateColumnDefinition generates column definition
|
||||
func (w *Writer) generateColumnDefinition(col *models.Column) string {
|
||||
parts := []string{col.SQLName()}
|
||||
parts := []string{quoteIdent(col.Name)}
|
||||
|
||||
parts = append(parts, effectiveColumnSQLType(col))
|
||||
|
||||
@@ -700,7 +749,7 @@ func (w *Writer) generateColumnDefinition(col *models.Column) string {
|
||||
if !col.Generated && !col.Identity && col.Default != nil {
|
||||
switch v := col.Default.(type) {
|
||||
case string:
|
||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", writers.QuoteDefaultValue(stripBackticks(v), col.Type)))
|
||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", quoteDefaultValue(stripBackticks(v), col.Type)))
|
||||
case bool:
|
||||
parts = append(parts, fmt.Sprintf("DEFAULT %v", v))
|
||||
default:
|
||||
@@ -764,13 +813,18 @@ func equivalentTypeListSQL(sqlType string) string {
|
||||
variants := pgsql.EquivalentSQLTypeVariants(sqlType)
|
||||
quoted := make([]string, 0, len(variants))
|
||||
for _, variant := range variants {
|
||||
quoted = append(quoted, fmt.Sprintf("'%s'", escapeQuote(variant)))
|
||||
quoted = append(quoted, quote(variant))
|
||||
}
|
||||
return strings.Join(quoted, ", ")
|
||||
}
|
||||
|
||||
// WriteSchema writes a single schema and all its tables
|
||||
func (w *Writer) WriteSchema(schema *models.Schema) error {
|
||||
var err error
|
||||
schema, err = w.prepareSchema(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.writer == nil {
|
||||
w.writer = os.Stdout
|
||||
}
|
||||
@@ -885,6 +939,11 @@ func (w *Writer) WriteTable(table *models.Table) error {
|
||||
// WriteAddColumnStatements writes ALTER TABLE ADD COLUMN statements for a database
|
||||
// This is used for schema evolution/migration when new columns are added
|
||||
func (w *Writer) WriteAddColumnStatements(db *models.Database) error {
|
||||
prepared, preparationErr := prepareModel(db, w.options.LowercaseIdentifiers)
|
||||
if preparationErr != nil {
|
||||
return preparationErr
|
||||
}
|
||||
db = prepared
|
||||
var writer io.Writer
|
||||
var file *os.File
|
||||
var err error
|
||||
@@ -926,8 +985,8 @@ func (w *Writer) writeCreateSchema(schema *models.Schema) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(w.writer, "-- Schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "CREATE SCHEMA IF NOT EXISTS %s;\n\n", schema.SQLName())
|
||||
fmt.Fprintf(w.writer, "-- Schema: %s\n", commentText(schema.Name))
|
||||
fmt.Fprintf(w.writer, "CREATE SCHEMA IF NOT EXISTS %s;\n\n", quoteIdent(schema.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -946,7 +1005,7 @@ func (w *Writer) writeRequiredExtensions(schema *models.Schema) error {
|
||||
|
||||
// writeSequences generates CREATE SEQUENCE statements for identity columns
|
||||
func (w *Writer) writeSequences(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Sequences for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Sequences for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Only create the sequence the primary key's nextval() default uses
|
||||
@@ -978,11 +1037,11 @@ func (w *Writer) writeSequences(schema *models.Schema) error {
|
||||
|
||||
// writeCreateTables generates CREATE TABLE statements
|
||||
func (w *Writer) writeCreateTables(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Tables for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Tables for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
fmt.Fprintf(w.writer, "CREATE TABLE IF NOT EXISTS %s (\n",
|
||||
w.qualTable(schema.SQLName(), table.SQLName()))
|
||||
w.qualTable(schema.Name, table.Name))
|
||||
|
||||
// Write columns
|
||||
columns := getSortedColumns(table.Columns)
|
||||
@@ -1003,7 +1062,7 @@ func (w *Writer) writeCreateTables(schema *models.Schema) error {
|
||||
|
||||
// writeAddColumns generates ALTER TABLE ADD COLUMN statements for missing columns
|
||||
func (w *Writer) writeAddColumns(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Add missing columns for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Add missing columns for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort columns by sequence or name for consistent output
|
||||
@@ -1032,7 +1091,7 @@ func (w *Writer) writeAddColumns(schema *models.Schema) error {
|
||||
}
|
||||
|
||||
func (w *Writer) writeAlterColumnTypes(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Alter column types for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Alter column types for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
statements, err := w.GenerateAlterColumnTypeStatements(schema)
|
||||
if err != nil {
|
||||
@@ -1047,7 +1106,7 @@ func (w *Writer) writeAlterColumnTypes(schema *models.Schema) error {
|
||||
}
|
||||
|
||||
func (w *Writer) writeAlterColumnDefaults(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Alter column defaults for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Alter column defaults for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
statements, err := w.GenerateAlterColumnDefaultStatements(schema)
|
||||
if err != nil {
|
||||
@@ -1062,7 +1121,7 @@ func (w *Writer) writeAlterColumnDefaults(schema *models.Schema) error {
|
||||
}
|
||||
|
||||
func (w *Writer) writeAlterColumnNullability(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Alter column nullability for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Alter column nullability for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
statements, err := w.GenerateAlterColumnNullabilityStatements(schema)
|
||||
if err != nil {
|
||||
@@ -1078,7 +1137,7 @@ func (w *Writer) writeAlterColumnNullability(schema *models.Schema) error {
|
||||
|
||||
// writePrimaryKeys generates ALTER TABLE statements for primary keys
|
||||
func (w *Writer) writePrimaryKeys(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Primary keys for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Primary keys for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Find primary key constraint
|
||||
@@ -1091,21 +1150,21 @@ func (w *Writer) writePrimaryKeys(schema *models.Schema) error {
|
||||
}
|
||||
|
||||
var columnNames []string
|
||||
pkName := fmt.Sprintf("pk_%s_%s", schema.SQLName(), table.SQLName())
|
||||
pkName := fmt.Sprintf("pk_%s_%s", schema.Name, table.Name)
|
||||
|
||||
if pkConstraint != nil {
|
||||
// Build column list from explicit constraint
|
||||
columnNames = make([]string, 0, len(pkConstraint.Columns))
|
||||
for _, colName := range pkConstraint.Columns {
|
||||
if col, ok := table.Columns[colName]; ok {
|
||||
columnNames = append(columnNames, col.SQLName())
|
||||
columnNames = append(columnNames, col.Name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No explicit PK constraint, check for columns with IsPrimaryKey = true
|
||||
for _, col := range table.Columns {
|
||||
if col.IsPrimaryKey {
|
||||
columnNames = append(columnNames, col.SQLName())
|
||||
columnNames = append(columnNames, col.Name)
|
||||
}
|
||||
}
|
||||
// Sort for consistent output
|
||||
@@ -1127,7 +1186,7 @@ func (w *Writer) writePrimaryKeys(schema *models.Schema) error {
|
||||
TableName: table.Name,
|
||||
ConstraintName: pkName,
|
||||
AutoGenNames: formatStringList(autoGenPKNames),
|
||||
Columns: strings.Join(columnNames, ", "),
|
||||
Columns: quotedIdentifiers(columnNames),
|
||||
ColumnNames: formatStringList(columnNames),
|
||||
}
|
||||
|
||||
@@ -1144,7 +1203,7 @@ func (w *Writer) writePrimaryKeys(schema *models.Schema) error {
|
||||
|
||||
// writeIndexes generates CREATE INDEX statements
|
||||
func (w *Writer) writeIndexes(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Indexes for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Indexes for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort indexes by name for consistent output
|
||||
@@ -1170,7 +1229,7 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
||||
indexType = "ukey"
|
||||
}
|
||||
columnSuffix := strings.Join(index.Columns, "_")
|
||||
indexName = fmt.Sprintf("%s_%s_%s", indexType, table.SQLName(), strings.ToLower(columnSuffix))
|
||||
indexName = fmt.Sprintf("%s_%s_%s", indexType, table.Name, strings.ToLower(columnSuffix))
|
||||
}
|
||||
|
||||
indexType := index.Type
|
||||
@@ -1196,7 +1255,7 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
||||
|
||||
tablespaceClause := ""
|
||||
if ts := pgIndexDirectiveTablespace(index); ts != "" {
|
||||
tablespaceClause = fmt.Sprintf(" TABLESPACE %s", ts)
|
||||
tablespaceClause = fmt.Sprintf(" TABLESPACE %s", quoteIdent(ts))
|
||||
}
|
||||
|
||||
whereClause := ""
|
||||
@@ -1210,9 +1269,9 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
||||
}
|
||||
|
||||
fmt.Fprintf(w.writer, "CREATE %sINDEX %sIF NOT EXISTS %s\n",
|
||||
unique, concurrently, indexName)
|
||||
unique, concurrently, quoteIdent(indexName))
|
||||
fmt.Fprintf(w.writer, " ON %s USING %s (%s)%s%s%s;\n\n",
|
||||
w.qualTable(schema.SQLName(), table.SQLName()), indexType, strings.Join(columnExprs, ", "), withClause, tablespaceClause, whereClause)
|
||||
w.qualTable(schema.Name, table.Name), indexType, strings.Join(columnExprs, ", "), withClause, tablespaceClause, whereClause)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1221,7 +1280,7 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
||||
|
||||
// writeUniqueConstraints generates ALTER TABLE statements for unique constraints
|
||||
func (w *Writer) writeUniqueConstraints(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Unique constraints for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Unique constraints for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort constraints by name for consistent output
|
||||
@@ -1240,7 +1299,7 @@ func (w *Writer) writeUniqueConstraints(schema *models.Schema) error {
|
||||
columnExprs := make([]string, 0, len(constraint.Columns))
|
||||
for _, colName := range constraint.Columns {
|
||||
if col, ok := table.Columns[colName]; ok {
|
||||
columnExprs = append(columnExprs, col.SQLName())
|
||||
columnExprs = append(columnExprs, quoteIdent(col.Name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1268,7 +1327,7 @@ func (w *Writer) writeUniqueConstraints(schema *models.Schema) error {
|
||||
|
||||
// writeCheckConstraints generates ALTER TABLE statements for check constraints
|
||||
func (w *Writer) writeCheckConstraints(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Check constraints for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Check constraints for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort constraints by name for consistent output
|
||||
@@ -1307,7 +1366,7 @@ func (w *Writer) writeCheckConstraints(schema *models.Schema) error {
|
||||
|
||||
// writeForeignKeys generates ALTER TABLE statements for foreign keys
|
||||
func (w *Writer) writeForeignKeys(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Foreign keys for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Foreign keys for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort relationships by name for consistent output
|
||||
@@ -1327,7 +1386,7 @@ func (w *Writer) writeForeignKeys(schema *models.Schema) error {
|
||||
fkName = name
|
||||
}
|
||||
if fkName == "" {
|
||||
fkName = fmt.Sprintf("fk_%s_%s", table.SQLName(), rel.ToTable)
|
||||
fkName = fmt.Sprintf("fk_%s_%s", table.Name, rel.ToTable)
|
||||
}
|
||||
|
||||
// Find the foreign key constraint that matches this relationship.
|
||||
@@ -1354,13 +1413,13 @@ func (w *Writer) writeForeignKeys(schema *models.Schema) error {
|
||||
sourceColumns := make([]string, 0, len(fkConstraint.Columns))
|
||||
for _, colName := range fkConstraint.Columns {
|
||||
if col, ok := table.Columns[colName]; ok {
|
||||
sourceColumns = append(sourceColumns, col.SQLName())
|
||||
sourceColumns = append(sourceColumns, quoteIdent(col.Name))
|
||||
}
|
||||
}
|
||||
|
||||
targetColumns := make([]string, 0, len(fkConstraint.ReferencedColumns))
|
||||
for _, colName := range fkConstraint.ReferencedColumns {
|
||||
targetColumns = append(targetColumns, strings.ToLower(colName))
|
||||
targetColumns = append(targetColumns, quoteIdent(colName))
|
||||
}
|
||||
|
||||
if len(sourceColumns) == 0 || len(targetColumns) == 0 {
|
||||
@@ -1435,15 +1494,15 @@ func (w *Writer) writeForeignKeys(schema *models.Schema) error {
|
||||
sourceColumns := make([]string, 0, len(constraint.Columns))
|
||||
for _, colName := range constraint.Columns {
|
||||
if col, ok := table.Columns[colName]; ok {
|
||||
sourceColumns = append(sourceColumns, col.SQLName())
|
||||
sourceColumns = append(sourceColumns, quoteIdent(col.Name))
|
||||
} else {
|
||||
sourceColumns = append(sourceColumns, colName)
|
||||
sourceColumns = append(sourceColumns, quoteIdent(colName))
|
||||
}
|
||||
}
|
||||
|
||||
targetColumns := make([]string, 0, len(constraint.ReferencedColumns))
|
||||
for _, colName := range constraint.ReferencedColumns {
|
||||
targetColumns = append(targetColumns, strings.ToLower(colName))
|
||||
targetColumns = append(targetColumns, quoteIdent(colName))
|
||||
}
|
||||
|
||||
if len(sourceColumns) == 0 || len(targetColumns) == 0 {
|
||||
@@ -1492,7 +1551,7 @@ func (w *Writer) writeForeignKeys(schema *models.Schema) error {
|
||||
|
||||
// writeSetSequenceValues generates statements to set sequence current values
|
||||
func (w *Writer) writeSetSequenceValues(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Set sequence values for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Set sequence values for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
seqName := primaryKeySequenceName(table)
|
||||
@@ -1521,22 +1580,22 @@ func (w *Writer) writeSetSequenceValues(schema *models.Schema) error {
|
||||
|
||||
// writeComments generates COMMENT statements for tables and columns
|
||||
func (w *Writer) writeComments(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Comments for schema: %s\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, "-- Comments for schema: %s\n", commentText(schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Table comment
|
||||
if table.Description != "" {
|
||||
fmt.Fprintf(w.writer, "COMMENT ON TABLE %s IS '%s';\n",
|
||||
w.qualTable(schema.SQLName(), table.SQLName()),
|
||||
escapeQuote(table.Description))
|
||||
fmt.Fprintf(w.writer, "COMMENT ON TABLE %s IS %s;\n",
|
||||
w.qualTable(schema.Name, table.Name),
|
||||
quote(table.Description))
|
||||
}
|
||||
|
||||
// Column comments
|
||||
for _, col := range getSortedColumns(table.Columns) {
|
||||
if col.Description != "" {
|
||||
fmt.Fprintf(w.writer, "COMMENT ON COLUMN %s.%s IS '%s';\n",
|
||||
w.qualTable(schema.SQLName(), table.SQLName()), col.SQLName(),
|
||||
escapeQuote(col.Description))
|
||||
fmt.Fprintf(w.writer, "COMMENT ON COLUMN %s.%s IS %s;\n",
|
||||
w.qualTable(schema.Name, table.Name), quoteIdent(col.Name),
|
||||
quote(col.Description))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1897,6 +1956,9 @@ func nativeGistBaseType(baseType string) bool {
|
||||
}
|
||||
|
||||
func resolveIndexColumn(table *models.Table, colName string) (*models.Column, bool) {
|
||||
if len(colName) >= 2 && colName[0] == '"' && colName[len(colName)-1] == '"' {
|
||||
colName = strings.ReplaceAll(colName[1:len(colName)-1], `""`, `"`)
|
||||
}
|
||||
if table == nil {
|
||||
return nil, false
|
||||
}
|
||||
@@ -1904,18 +1966,8 @@ func resolveIndexColumn(table *models.Table, colName string) (*models.Column, bo
|
||||
return col, true
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.Trim(colName, `"`))
|
||||
for key, col := range table.Columns {
|
||||
if col == nil {
|
||||
continue
|
||||
}
|
||||
if strings.ToLower(strings.Trim(key, `"`)) == normalized {
|
||||
return col, true
|
||||
}
|
||||
if strings.ToLower(strings.Trim(col.Name, `"`)) == normalized {
|
||||
return col, true
|
||||
}
|
||||
if strings.ToLower(strings.Trim(col.SQLName(), `"`)) == normalized {
|
||||
for _, col := range table.Columns {
|
||||
if col != nil && col.Name == colName {
|
||||
return col, true
|
||||
}
|
||||
}
|
||||
@@ -1972,7 +2024,7 @@ func sortIndexes(indexes map[string]*models.Index) []*models.Index {
|
||||
func formatStringList(items []string) string {
|
||||
quoted := make([]string, len(items))
|
||||
for i, item := range items {
|
||||
quoted[i] = fmt.Sprintf("'%s'", escapeQuote(item))
|
||||
quoted[i] = quote(item)
|
||||
}
|
||||
return strings.Join(quoted, ", ")
|
||||
}
|
||||
@@ -2049,19 +2101,20 @@ func extractSequenceName(defaultExpr string) string {
|
||||
if start == -1 {
|
||||
return ""
|
||||
}
|
||||
end := strings.Index(defaultExpr[start+1:], "'")
|
||||
if end == -1 {
|
||||
return ""
|
||||
var name strings.Builder
|
||||
for i := start + 1; i < len(defaultExpr); i++ {
|
||||
if defaultExpr[i] == '\'' {
|
||||
if i+1 < len(defaultExpr) && defaultExpr[i+1] == '\'' {
|
||||
name.WriteByte('\'')
|
||||
i++
|
||||
continue
|
||||
}
|
||||
return regclassLastName(name.String())
|
||||
}
|
||||
name.WriteByte(defaultExpr[i])
|
||||
}
|
||||
return ""
|
||||
|
||||
fullName := defaultExpr[start+1 : start+1+end]
|
||||
|
||||
// Remove schema prefix if present
|
||||
parts := strings.Split(fullName, ".")
|
||||
if len(parts) > 1 {
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
return fullName
|
||||
}
|
||||
|
||||
// executeDatabaseSQL applies db to a PostgreSQL database. By default it reads the live schema
|
||||
@@ -2126,7 +2179,7 @@ func (w *Writer) diffStatements(model, current *models.Database) ([]string, erro
|
||||
currentSchemas := make(map[string]*models.Schema)
|
||||
for _, cs := range current.Schemas {
|
||||
if cs != nil {
|
||||
currentSchemas[strings.ToLower(cs.Name)] = cs
|
||||
currentSchemas[cs.Name] = cs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2142,25 +2195,25 @@ func (w *Writer) diffStatements(model, current *models.Database) ([]string, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs := currentSchemas[strings.ToLower(schema.Name)]
|
||||
cs := currentSchemas[schema.Name]
|
||||
if cs == nil && schema.Name != "public" {
|
||||
statements = append(statements,
|
||||
fmt.Sprintf("-- Schema: %s", schema.Name),
|
||||
fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", schema.SQLName()))
|
||||
fmt.Sprintf("-- Schema: %s", commentText(schema.Name)),
|
||||
fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", quoteIdent(schema.Name)))
|
||||
}
|
||||
|
||||
existing := make(map[string]*models.Table)
|
||||
knownSequences := make(map[string]bool)
|
||||
if cs != nil {
|
||||
for _, t := range cs.Tables {
|
||||
existing[strings.ToLower(t.Name)] = t
|
||||
existing[t.Name] = t
|
||||
}
|
||||
for _, seq := range cs.Sequences {
|
||||
knownSequences[strings.ToLower(seq.Name)] = true
|
||||
knownSequences[seq.Name] = true
|
||||
}
|
||||
}
|
||||
for _, table := range schema.Tables {
|
||||
currentTable, ok := existing[strings.ToLower(table.Name)]
|
||||
currentTable, ok := existing[table.Name]
|
||||
if !ok {
|
||||
// New table: no rows yet, so the sequence only needs creating.
|
||||
if stmt := w.primaryKeySequenceStatement(schema, table); stmt != "" {
|
||||
@@ -2173,7 +2226,7 @@ func (w *Writer) diffStatements(model, current *models.Database) ([]string, erro
|
||||
// database does not have yet: create it and move it past the data,
|
||||
// otherwise it restarts at 1 and collides with existing rows.
|
||||
seqName := primaryKeySequenceName(table)
|
||||
if seqName == "" || knownSequences[strings.ToLower(seqName)] {
|
||||
if seqName == "" || knownSequences[seqName] {
|
||||
continue
|
||||
}
|
||||
if cpk := currentTable.GetPrimaryKey(); cpk != nil && columnDefaultsEqual(table.GetPrimaryKey().Default, cpk.Default) {
|
||||
@@ -2209,7 +2262,7 @@ func (w *Writer) diffStatements(model, current *models.Database) ([]string, erro
|
||||
continue
|
||||
}
|
||||
if script.Schema != "" && script.Schema != lastSchema {
|
||||
statements = append(statements, fmt.Sprintf("-- Schema: %s", script.Schema))
|
||||
statements = append(statements, fmt.Sprintf("-- Schema: %s", commentText(script.Schema)))
|
||||
lastSchema = script.Schema
|
||||
}
|
||||
statements = append(statements, body)
|
||||
@@ -2311,23 +2364,8 @@ func extractTableNameFromCreate(stmt string) string {
|
||||
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
|
||||
_, name := parseQualifiedIdent(rest)
|
||||
return name
|
||||
}
|
||||
|
||||
// truncateStatement truncates long SQL statements for error messages
|
||||
@@ -2445,11 +2483,21 @@ func extractSQLStringValue(stmt, key string) string {
|
||||
// parseQualifiedIdent extracts (schema, name) from the start of s, handling optional "schema"."table" quoting.
|
||||
func parseQualifiedIdent(s string) (schema, name string) {
|
||||
token := firstBareIdent(s)
|
||||
parts := strings.SplitN(token, ".", 2)
|
||||
if len(parts) == 2 {
|
||||
return stripQuotes(parts[0]), stripQuotes(parts[1])
|
||||
quoted := false
|
||||
for i := 0; i < len(token); i++ {
|
||||
if token[i] == '"' {
|
||||
if quoted && i+1 < len(token) && token[i+1] == '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
quoted = !quoted
|
||||
}
|
||||
if token[i] == '.' && !quoted {
|
||||
return stripQuotes(token[:i]), stripQuotes(token[i+1:])
|
||||
}
|
||||
}
|
||||
return "", stripQuotes(token)
|
||||
|
||||
}
|
||||
|
||||
// firstBareIdent returns the first whitespace/delimiter-terminated token, stripping outer quotes.
|
||||
@@ -2458,11 +2506,21 @@ func firstBareIdent(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
end := strings.IndexAny(s, " \t\n(,;")
|
||||
if end == -1 {
|
||||
return s
|
||||
quoted := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '"' {
|
||||
if quoted && i+1 < len(s) && s[i+1] == '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
quoted = !quoted
|
||||
}
|
||||
if !quoted && strings.ContainsRune(" \t\r\n(,;", rune(s[i])) {
|
||||
return s[:i]
|
||||
}
|
||||
}
|
||||
return s[:end]
|
||||
return s
|
||||
|
||||
}
|
||||
|
||||
// firstIdentAfterKeyword returns the first identifier token after keyword (matched case-insensitively via upperStmt).
|
||||
@@ -2476,7 +2534,10 @@ func firstIdentAfterKeyword(stmt, upperStmt, keyword string) string {
|
||||
|
||||
// stripQuotes removes surrounding double-quotes from an identifier.
|
||||
func stripQuotes(s string) string {
|
||||
return strings.Trim(s, "\"")
|
||||
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
|
||||
return strings.ReplaceAll(s[1:len(s)-1], `""`, `"`)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// buildStmtContext assembles a display string from available identifiers.
|
||||
@@ -2598,3 +2659,5 @@ func detectStatementType(stmt string) string {
|
||||
func quoteIdentifier(s string) string {
|
||||
return quoteIdent(s)
|
||||
}
|
||||
|
||||
func commentText(s string) string { return strings.NewReplacer("\r", " ", "\n", " ").Replace(s) }
|
||||
|
||||
Reference in New Issue
Block a user