Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae0efdc008 | ||
|
|
be08c8199f |
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Maintainer: Hein (Warky Devs) <hein@warky.dev>
|
||||
pkgname=relspec
|
||||
pkgver=1.0.69
|
||||
pkgver=1.0.70
|
||||
pkgrel=1
|
||||
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')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: relspec
|
||||
Version: 1.0.69
|
||||
Version: 1.0.70
|
||||
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.
|
||||
|
||||
|
||||
+6
-1
@@ -492,7 +492,12 @@ func extractTypeParts(col *models.Column) (baseType string, length, precision, s
|
||||
}
|
||||
}
|
||||
|
||||
typeName = pgsql.NormalizePGType(typeName)
|
||||
// serial/bigserial/smallserial are sugar over an integer column plus a
|
||||
// sequence default; PostgreSQL itself reports the underlying integer
|
||||
// type back for such columns, so treat them as equivalent here to avoid
|
||||
// spurious conflicts between a DBML "bigserial" source and a live-read
|
||||
// "bigint" target (or vice versa).
|
||||
typeName = pgsql.SerialUnderlyingType(typeName)
|
||||
|
||||
return typeName, length, precision, scale
|
||||
}
|
||||
|
||||
@@ -196,6 +196,50 @@ func TestMergeColumns_TypeConflictIsDetected(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeColumns_SerialVsUnderlyingIntegerIsNotAConflict(t *testing.T) {
|
||||
target := &models.Database{
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "users",
|
||||
Schema: "public",
|
||||
Columns: map[string]*models.Column{
|
||||
// As reported back by a live PostgreSQL read of an
|
||||
// existing serial primary key column.
|
||||
"id": {Name: "id", Type: "bigint"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
source := &models.Database{
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "users",
|
||||
Schema: "public",
|
||||
Columns: map[string]*models.Column{
|
||||
// As declared in a DBML source spec.
|
||||
"id": {Name: "id", Type: "bigserial"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := MergeDatabases(target, source, nil)
|
||||
|
||||
if len(result.TypeConflicts) != 0 {
|
||||
t.Fatalf("Expected no type conflicts for bigserial vs bigint, got %d: %+v", len(result.TypeConflicts), result.TypeConflicts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConstraints_NewConstraint(t *testing.T) {
|
||||
target := &models.Database{
|
||||
Schemas: []*models.Schema{
|
||||
|
||||
@@ -193,6 +193,28 @@ func IsKnownPGBaseType(baseType string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// serialUnderlyingType maps each serial pseudo-type to the integer type
|
||||
// PostgreSQL actually stores the column as. serial/bigserial/smallserial are
|
||||
// not real types: they are sugar for an integer column plus a sequence
|
||||
// default, and pg_catalog (and information_schema) always reports the
|
||||
// underlying integer type back for such columns.
|
||||
var serialUnderlyingType = map[string]string{
|
||||
"serial": "integer",
|
||||
"bigserial": "bigint",
|
||||
"smallserial": "smallint",
|
||||
}
|
||||
|
||||
// SerialUnderlyingType returns the underlying integer type for a serial
|
||||
// pseudo-type (e.g. "bigserial" -> "bigint"). If baseType (after
|
||||
// NormalizePGType) is not a serial type, it is returned unchanged.
|
||||
func SerialUnderlyingType(baseType string) string {
|
||||
normalized := NormalizePGType(baseType)
|
||||
if underlying, ok := serialUnderlyingType[normalized]; ok {
|
||||
return underlying
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func IsGoType(pTypeName string) bool {
|
||||
for k := range GoToStdTypes {
|
||||
if strings.EqualFold(pTypeName, k) {
|
||||
|
||||
@@ -148,6 +148,26 @@ func SanitizeFilename(name string) string {
|
||||
// Examples (boolean): "true" → "true"
|
||||
// Examples (bigint): "0" → "0"
|
||||
// Examples (timestamp): "now()" → "now()" (function call – never quoted)
|
||||
// bareKeywordDefaults are PostgreSQL default-value keywords that are
|
||||
// expressions, not string literals, even though they contain no
|
||||
// parentheses (e.g. "CURRENT_DATE" rather than "now()"). They must never be
|
||||
// wrapped in quotes.
|
||||
var bareKeywordDefaults = map[string]bool{
|
||||
"current_date": true,
|
||||
"current_time": true,
|
||||
"current_timestamp": true,
|
||||
"localtime": true,
|
||||
"localtimestamp": true,
|
||||
"current_user": true,
|
||||
"session_user": true,
|
||||
"current_role": true,
|
||||
"current_catalog": true,
|
||||
"current_schema": true,
|
||||
"null": true,
|
||||
"true": true,
|
||||
"false": true,
|
||||
}
|
||||
|
||||
func QuoteDefaultValue(value, sqlType string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
@@ -158,6 +178,12 @@ func QuoteDefaultValue(value, sqlType string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Bare keyword expressions (e.g. CURRENT_DATE) are never quoted,
|
||||
// regardless of column type.
|
||||
if bareKeywordDefaults[strings.ToLower(value)] {
|
||||
return value
|
||||
}
|
||||
|
||||
// Normalise the SQL type: lowercase, strip length/precision suffix.
|
||||
baseType := strings.ToLower(strings.TrimSpace(sqlType))
|
||||
if idx := strings.Index(baseType, "("); idx > 0 {
|
||||
|
||||
@@ -41,6 +41,24 @@ func TestQuoteDefaultValue(t *testing.T) {
|
||||
sqlType: "timestamptz",
|
||||
want: "now()",
|
||||
},
|
||||
{
|
||||
name: "bare keyword default CURRENT_DATE is not quoted",
|
||||
value: "CURRENT_DATE",
|
||||
sqlType: "date",
|
||||
want: "CURRENT_DATE",
|
||||
},
|
||||
{
|
||||
name: "bare keyword default is case insensitive",
|
||||
value: "current_timestamp",
|
||||
sqlType: "timestamptz",
|
||||
want: "current_timestamp",
|
||||
},
|
||||
{
|
||||
name: "bare keyword default localtime is not quoted",
|
||||
value: "LOCALTIME",
|
||||
sqlType: "time",
|
||||
want: "LOCALTIME",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user