Compare commits
3 Commits
v1.0.35
...
v1.0.37-1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
480038d51d | ||
| 77436757c8 | |||
| 5e6f03e412 |
@@ -62,6 +62,17 @@ func (tm *TypeMapper) isSimpleType(sqlType string) bool {
|
|||||||
return simpleTypes[sqlType]
|
return simpleTypes[sqlType]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isSerialType checks if a SQL type is a serial type (auto-incrementing)
|
||||||
|
func (tm *TypeMapper) isSerialType(sqlType string) bool {
|
||||||
|
baseType := tm.extractBaseType(sqlType)
|
||||||
|
serialTypes := map[string]bool{
|
||||||
|
"serial": true,
|
||||||
|
"bigserial": true,
|
||||||
|
"smallserial": true,
|
||||||
|
}
|
||||||
|
return serialTypes[baseType]
|
||||||
|
}
|
||||||
|
|
||||||
// baseGoType returns the base Go type for a SQL type (not null, simple types only)
|
// baseGoType returns the base Go type for a SQL type (not null, simple types only)
|
||||||
func (tm *TypeMapper) baseGoType(sqlType string) string {
|
func (tm *TypeMapper) baseGoType(sqlType string) string {
|
||||||
typeMap := map[string]string{
|
typeMap := map[string]string{
|
||||||
@@ -122,10 +133,10 @@ func (tm *TypeMapper) bunGoType(sqlType string) string {
|
|||||||
"decimal": tm.sqlTypesAlias + ".SqlFloat64",
|
"decimal": tm.sqlTypesAlias + ".SqlFloat64",
|
||||||
|
|
||||||
// Date/Time types
|
// Date/Time types
|
||||||
"timestamp": tm.sqlTypesAlias + ".SqlTime",
|
"timestamp": tm.sqlTypesAlias + ".SqlTimeStamp",
|
||||||
"timestamp without time zone": tm.sqlTypesAlias + ".SqlTime",
|
"timestamp without time zone": tm.sqlTypesAlias + ".SqlTimeStamp",
|
||||||
"timestamp with time zone": tm.sqlTypesAlias + ".SqlTime",
|
"timestamp with time zone": tm.sqlTypesAlias + ".SqlTimeStamp",
|
||||||
"timestamptz": tm.sqlTypesAlias + ".SqlTime",
|
"timestamptz": tm.sqlTypesAlias + ".SqlTimeStamp",
|
||||||
"date": tm.sqlTypesAlias + ".SqlDate",
|
"date": tm.sqlTypesAlias + ".SqlDate",
|
||||||
"time": tm.sqlTypesAlias + ".SqlTime",
|
"time": tm.sqlTypesAlias + ".SqlTime",
|
||||||
"time without time zone": tm.sqlTypesAlias + ".SqlTime",
|
"time without time zone": tm.sqlTypesAlias + ".SqlTime",
|
||||||
@@ -190,10 +201,15 @@ func (tm *TypeMapper) BuildBunTag(column *models.Column, table *models.Table) st
|
|||||||
parts = append(parts, "pk")
|
parts = append(parts, "pk")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto increment (for serial types or explicit auto_increment)
|
||||||
|
if column.AutoIncrement || tm.isSerialType(column.Type) {
|
||||||
|
parts = append(parts, "autoincrement")
|
||||||
|
}
|
||||||
|
|
||||||
// Default value
|
// Default value
|
||||||
if column.Default != nil {
|
if column.Default != nil {
|
||||||
// Sanitize default value to remove backticks
|
// Sanitize default value to remove backticks, then quote based on column type
|
||||||
safeDefault := writers.SanitizeStructTagValue(fmt.Sprintf("%v", column.Default))
|
safeDefault := writers.QuoteDefaultValue(writers.SanitizeStructTagValue(fmt.Sprintf("%v", column.Default)), column.Type)
|
||||||
parts = append(parts, fmt.Sprintf("default:%s", safeDefault))
|
parts = append(parts, fmt.Sprintf("default:%s", safeDefault))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ func TestWriter_WriteTable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify Bun-specific elements
|
// Verify Bun-specific elements
|
||||||
if !strings.Contains(generated, "bun:\"id,type:bigint,pk,") {
|
if !strings.Contains(generated, "bun:\"id,type:bigint,pk,autoincrement,") {
|
||||||
t.Errorf("Missing Bun-style primary key tag")
|
t.Errorf("Missing Bun-style primary key tag with autoincrement")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,8 +567,8 @@ func TestTypeMapper_SQLTypeToGoType_Bun(t *testing.T) {
|
|||||||
{"bigint", false, "resolvespec_common.SqlInt64"},
|
{"bigint", false, "resolvespec_common.SqlInt64"},
|
||||||
{"varchar", true, "resolvespec_common.SqlString"}, // Bun uses sql types even for NOT NULL strings
|
{"varchar", true, "resolvespec_common.SqlString"}, // Bun uses sql types even for NOT NULL strings
|
||||||
{"varchar", false, "resolvespec_common.SqlString"},
|
{"varchar", false, "resolvespec_common.SqlString"},
|
||||||
{"timestamp", true, "resolvespec_common.SqlTime"},
|
{"timestamp", true, "resolvespec_common.SqlTimeStamp"},
|
||||||
{"timestamp", false, "resolvespec_common.SqlTime"},
|
{"timestamp", false, "resolvespec_common.SqlTimeStamp"},
|
||||||
{"date", false, "resolvespec_common.SqlDate"},
|
{"date", false, "resolvespec_common.SqlDate"},
|
||||||
{"boolean", true, "bool"},
|
{"boolean", true, "bool"},
|
||||||
{"boolean", false, "resolvespec_common.SqlBool"},
|
{"boolean", false, "resolvespec_common.SqlBool"},
|
||||||
@@ -615,14 +615,75 @@ func TestTypeMapper_BuildBunTag(t *testing.T) {
|
|||||||
want: []string{"email,", "type:varchar(255),", "nullzero,"},
|
want: []string{"email,", "type:varchar(255),", "nullzero,"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with default",
|
name: "with default string",
|
||||||
column: &models.Column{
|
column: &models.Column{
|
||||||
Name: "status",
|
Name: "status",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
NotNull: true,
|
NotNull: true,
|
||||||
Default: "active",
|
Default: "active",
|
||||||
},
|
},
|
||||||
want: []string{"status,", "type:text,", "default:active,"},
|
want: []string{"status,", "type:text,", "default:'active',"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with default integer",
|
||||||
|
column: &models.Column{
|
||||||
|
Name: "retries",
|
||||||
|
Type: "integer",
|
||||||
|
NotNull: true,
|
||||||
|
Default: "0",
|
||||||
|
},
|
||||||
|
want: []string{"retries,", "type:integer,", "default:0,"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with default boolean",
|
||||||
|
column: &models.Column{
|
||||||
|
Name: "active",
|
||||||
|
Type: "boolean",
|
||||||
|
NotNull: true,
|
||||||
|
Default: "true",
|
||||||
|
},
|
||||||
|
want: []string{"active,", "type:boolean,", "default:true,"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with default function call",
|
||||||
|
column: &models.Column{
|
||||||
|
Name: "created_at",
|
||||||
|
Type: "timestamp",
|
||||||
|
NotNull: true,
|
||||||
|
Default: "now()",
|
||||||
|
},
|
||||||
|
want: []string{"created_at,", "type:timestamp,", "default:now(),"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "auto increment with AutoIncrement flag",
|
||||||
|
column: &models.Column{
|
||||||
|
Name: "id",
|
||||||
|
Type: "bigint",
|
||||||
|
NotNull: true,
|
||||||
|
IsPrimaryKey: true,
|
||||||
|
AutoIncrement: true,
|
||||||
|
},
|
||||||
|
want: []string{"id,", "type:bigint,", "pk,", "autoincrement,"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "serial type (auto-increment)",
|
||||||
|
column: &models.Column{
|
||||||
|
Name: "id",
|
||||||
|
Type: "serial",
|
||||||
|
NotNull: true,
|
||||||
|
IsPrimaryKey: true,
|
||||||
|
},
|
||||||
|
want: []string{"id,", "type:serial,", "pk,", "autoincrement,"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bigserial type (auto-increment)",
|
||||||
|
column: &models.Column{
|
||||||
|
Name: "id",
|
||||||
|
Type: "bigserial",
|
||||||
|
NotNull: true,
|
||||||
|
IsPrimaryKey: true,
|
||||||
|
},
|
||||||
|
want: []string{"id,", "type:bigserial,", "pk,", "autoincrement,"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,10 +158,10 @@ func (tm *TypeMapper) nullableGoType(sqlType string) string {
|
|||||||
"decimal": tm.sqlTypesAlias + ".SqlFloat64",
|
"decimal": tm.sqlTypesAlias + ".SqlFloat64",
|
||||||
|
|
||||||
// Date/Time types
|
// Date/Time types
|
||||||
"timestamp": tm.sqlTypesAlias + ".SqlTime",
|
"timestamp": tm.sqlTypesAlias + ".SqlTimeStamp",
|
||||||
"timestamp without time zone": tm.sqlTypesAlias + ".SqlTime",
|
"timestamp without time zone": tm.sqlTypesAlias + ".SqlTimeStamp",
|
||||||
"timestamp with time zone": tm.sqlTypesAlias + ".SqlTime",
|
"timestamp with time zone": tm.sqlTypesAlias + ".SqlTimeStamp",
|
||||||
"timestamptz": tm.sqlTypesAlias + ".SqlTime",
|
"timestamptz": tm.sqlTypesAlias + ".SqlTimeStamp",
|
||||||
"date": tm.sqlTypesAlias + ".SqlDate",
|
"date": tm.sqlTypesAlias + ".SqlDate",
|
||||||
"time": tm.sqlTypesAlias + ".SqlTime",
|
"time": tm.sqlTypesAlias + ".SqlTime",
|
||||||
"time without time zone": tm.sqlTypesAlias + ".SqlTime",
|
"time without time zone": tm.sqlTypesAlias + ".SqlTime",
|
||||||
@@ -238,8 +238,8 @@ func (tm *TypeMapper) BuildGormTag(column *models.Column, table *models.Table) s
|
|||||||
|
|
||||||
// Default value
|
// Default value
|
||||||
if column.Default != nil {
|
if column.Default != nil {
|
||||||
// Sanitize default value to remove backticks
|
// Sanitize default value to remove backticks, then quote based on column type
|
||||||
safeDefault := writers.SanitizeStructTagValue(fmt.Sprintf("%v", column.Default))
|
safeDefault := writers.QuoteDefaultValue(writers.SanitizeStructTagValue(fmt.Sprintf("%v", column.Default)), column.Type)
|
||||||
parts = append(parts, fmt.Sprintf("default:%s", safeDefault))
|
parts = append(parts, fmt.Sprintf("default:%s", safeDefault))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -655,7 +655,7 @@ func TestTypeMapper_SQLTypeToGoType(t *testing.T) {
|
|||||||
{"varchar", true, "string"},
|
{"varchar", true, "string"},
|
||||||
{"varchar", false, "sql_types.SqlString"},
|
{"varchar", false, "sql_types.SqlString"},
|
||||||
{"timestamp", true, "time.Time"},
|
{"timestamp", true, "time.Time"},
|
||||||
{"timestamp", false, "sql_types.SqlTime"},
|
{"timestamp", false, "sql_types.SqlTimeStamp"},
|
||||||
{"boolean", true, "bool"},
|
{"boolean", true, "bool"},
|
||||||
{"boolean", false, "sql_types.SqlBool"},
|
{"boolean", false, "sql_types.SqlBool"},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,64 @@ func SanitizeFilename(name string) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QuoteDefaultValue wraps a sanitized default value in single quotes when the SQL
|
||||||
|
// column type requires it (strings, dates, times, UUIDs, enums). Numeric types
|
||||||
|
// (integers, floats, serials) and boolean types are left unquoted. Function-call
|
||||||
|
// expressions such as now() or gen_random_uuid() are always left unquoted regardless
|
||||||
|
// of type, because they contain parentheses.
|
||||||
|
//
|
||||||
|
// Examples (varchar): "disconnected" → "'disconnected'"
|
||||||
|
// Examples (boolean): "true" → "true"
|
||||||
|
// Examples (bigint): "0" → "0"
|
||||||
|
// Examples (timestamp): "now()" → "now()" (function call – never quoted)
|
||||||
|
func QuoteDefaultValue(value, sqlType string) string {
|
||||||
|
// Function calls are never quoted regardless of column type.
|
||||||
|
if strings.Contains(value, "(") || strings.Contains(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 {
|
||||||
|
baseType = baseType[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types whose default values must NOT be quoted.
|
||||||
|
unquotedTypes := map[string]bool{
|
||||||
|
// Integer types
|
||||||
|
"integer": true,
|
||||||
|
"int": true,
|
||||||
|
"int2": true,
|
||||||
|
"int4": true,
|
||||||
|
"int8": true,
|
||||||
|
"smallint": true,
|
||||||
|
"bigint": true,
|
||||||
|
"serial": true,
|
||||||
|
"smallserial": true,
|
||||||
|
"bigserial": true,
|
||||||
|
// Float / numeric types
|
||||||
|
"real": true,
|
||||||
|
"float": true,
|
||||||
|
"float4": true,
|
||||||
|
"float8": true,
|
||||||
|
"double precision": true,
|
||||||
|
"numeric": true,
|
||||||
|
"decimal": true,
|
||||||
|
"money": true,
|
||||||
|
// Boolean
|
||||||
|
"boolean": true,
|
||||||
|
"bool": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if unquotedTypes[baseType] {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else (text, varchar, char, uuid, date, time, timestamp, json, …)
|
||||||
|
// is treated as a quoted literal.
|
||||||
|
return "'" + 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.
|
||||||
// Go struct tags are delimited by backticks, so any backtick in the value would break the syntax.
|
// Go struct tags are delimited by backticks, so any backtick in the value would break the syntax.
|
||||||
// This function:
|
// This function:
|
||||||
|
|||||||
Reference in New Issue
Block a user