fix(mysql): normalize unsigned column types
This commit is contained in:
parent
4ef94eb3a9
commit
9781b0f562
|
|
@ -0,0 +1,17 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { combineDataTypeForDatabase, splitDataType } from "../tableStructureEditorState";
|
||||
|
||||
describe("tableStructureEditorState", () => {
|
||||
it("keeps mysql unsigned attributes in the editable base type", () => {
|
||||
expect(splitDataType("int(11) unsigned")).toEqual({ baseType: "int unsigned", params: "11" });
|
||||
expect(splitDataType("bigint(20) unsigned zerofill")).toEqual({
|
||||
baseType: "bigint unsigned zerofill",
|
||||
params: "20",
|
||||
});
|
||||
});
|
||||
|
||||
it("combines mysql unsigned type choices with the length field", () => {
|
||||
expect(combineDataTypeForDatabase("mysql", "int unsigned", "11")).toBe("int(11) unsigned");
|
||||
expect(combineDataTypeForDatabase("mysql", "bigint unsigned zerofill", "20")).toBe("bigint(20) unsigned zerofill");
|
||||
});
|
||||
});
|
||||
|
|
@ -643,8 +643,14 @@ export function splitDataType(raw: string): { baseType: string; params: string }
|
|||
const trimmed = raw.trim();
|
||||
const parenIdx = trimmed.indexOf("(");
|
||||
if (parenIdx === -1) return { baseType: trimmed, params: "" };
|
||||
const baseType = trimmed.slice(0, parenIdx).trim();
|
||||
const params = trimmed.slice(parenIdx + 1, trimmed.lastIndexOf(")")).trim();
|
||||
const closeIdx = trimmed.lastIndexOf(")");
|
||||
const baseTypePrefix = trimmed.slice(0, parenIdx).trim();
|
||||
const params = trimmed.slice(parenIdx + 1, closeIdx).trim();
|
||||
const suffix = trimmed
|
||||
.slice(closeIdx + 1)
|
||||
.trim()
|
||||
.replace(/\s+/g, " ");
|
||||
const baseType = /^(?:signed|unsigned|zerofill)(?:\s+(?:signed|unsigned|zerofill))*$/i.test(suffix) ? `${baseTypePrefix} ${suffix}`.trim() : baseTypePrefix;
|
||||
return { baseType, params };
|
||||
}
|
||||
|
||||
|
|
@ -660,7 +666,10 @@ export function combineDataTypeForDatabase(dbType: DatabaseType | undefined, bas
|
|||
if (isDataTypeLengthDisabled(dbType, baseType)) {
|
||||
return baseType;
|
||||
}
|
||||
return combineDataType(baseType, normalizeDataTypeParams(dbType, baseType, params));
|
||||
const normalizedParams = normalizeDataTypeParams(dbType, baseType, params);
|
||||
const mysqlType = combineMysqlNumericAttributeType(dbType, baseType, normalizedParams);
|
||||
if (mysqlType) return mysqlType;
|
||||
return combineDataType(baseType, normalizedParams);
|
||||
}
|
||||
|
||||
export function normalizeDataTypeParams(dbType: DatabaseType | undefined, baseType: string, params: string): string {
|
||||
|
|
@ -701,6 +710,21 @@ function isTemporalPrecisionType(dbType: DatabaseType | undefined, baseType: str
|
|||
}
|
||||
}
|
||||
|
||||
function combineMysqlNumericAttributeType(dbType: DatabaseType | undefined, baseType: string, params: string): string | null {
|
||||
if (!params || !isMysqlLikeStructureType(dbType)) return null;
|
||||
const parts = baseType.trim().replace(/\s+/g, " ").split(" ").filter(Boolean);
|
||||
const typeName = parts[0]?.toLowerCase();
|
||||
if (!typeName || !["tinyint", "smallint", "mediumint", "int", "integer", "bigint", "real", "double", "float", "decimal", "numeric"].includes(typeName)) return null;
|
||||
const attrIndex = parts.findIndex((part) => ["signed", "unsigned", "zerofill"].includes(part.toLowerCase()));
|
||||
if (attrIndex === -1) return null;
|
||||
if (!parts.slice(attrIndex).every((part) => ["signed", "unsigned", "zerofill"].includes(part.toLowerCase()))) return null;
|
||||
return `${parts.slice(0, attrIndex).join(" ")}(${params}) ${parts.slice(attrIndex).join(" ")}`;
|
||||
}
|
||||
|
||||
function isMysqlLikeStructureType(dbType: DatabaseType | undefined): boolean {
|
||||
return dbType === "mysql" || dbType === "doris" || dbType === "starrocks" || dbType === "goldendb" || dbType === "sundb" || dbType === "databend";
|
||||
}
|
||||
|
||||
function isValidTemporalPrecision(dbType: DatabaseType | undefined, params: string): boolean {
|
||||
if (!/^\d+$/.test(params)) return false;
|
||||
const value = Number(params);
|
||||
|
|
|
|||
|
|
@ -132,6 +132,12 @@ pub(super) fn normalize_column_data_type(dialect: StructureDialect, data_type: &
|
|||
return trimmed.to_string();
|
||||
}
|
||||
|
||||
if dialect == StructureDialect::Mysql {
|
||||
if let Some(normalized) = normalize_mysql_numeric_attribute_type(base_type, params) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
if is_temporal_precision_type(dialect, base_type) {
|
||||
return if is_valid_temporal_precision(params, dialect) {
|
||||
format!("{base_type}({params})")
|
||||
|
|
@ -143,6 +149,41 @@ pub(super) fn normalize_column_data_type(dialect: StructureDialect, data_type: &
|
|||
trimmed.to_string()
|
||||
}
|
||||
|
||||
fn normalize_mysql_numeric_attribute_type(base_type: &str, params: &str) -> Option<String> {
|
||||
let mut parts: Vec<&str> = base_type.split_whitespace().collect();
|
||||
let type_name = parts.first().copied()?.to_ascii_lowercase();
|
||||
if !matches!(
|
||||
type_name.as_str(),
|
||||
"tinyint"
|
||||
| "smallint"
|
||||
| "mediumint"
|
||||
| "int"
|
||||
| "integer"
|
||||
| "bigint"
|
||||
| "real"
|
||||
| "double"
|
||||
| "float"
|
||||
| "decimal"
|
||||
| "numeric"
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
let split_index = parts.iter().position(|part| {
|
||||
let normalized = part.to_ascii_lowercase();
|
||||
matches!(normalized.as_str(), "signed" | "unsigned" | "zerofill")
|
||||
})?;
|
||||
if !parts[split_index..].iter().all(|part| {
|
||||
let normalized = part.to_ascii_lowercase();
|
||||
matches!(normalized.as_str(), "signed" | "unsigned" | "zerofill")
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let attrs = parts.split_off(split_index).join(" ");
|
||||
let base = parts.join(" ");
|
||||
Some(format!("{base}({params}) {attrs}"))
|
||||
}
|
||||
|
||||
pub(super) fn is_temporal_precision_type(dialect: StructureDialect, base_type: &str) -> bool {
|
||||
let normalized = base_type.split_whitespace().collect::<Vec<_>>().join(" ").to_ascii_lowercase();
|
||||
match dialect {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,27 @@ fn builds_mysql_column_and_index_changes() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_mysql_unsigned_integer_column_with_length_before_attribute() {
|
||||
let mut score = column("score");
|
||||
score.data_type = "int unsigned(11)".to_string();
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![score],
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: Vec::new(),
|
||||
triggers: Vec::new(),
|
||||
table_comment: None,
|
||||
original_table_comment: None,
|
||||
});
|
||||
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert_eq!(result.statements, vec!["ALTER TABLE `users` ADD COLUMN `score` int(11) unsigned;"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_highgo_foreign_key_changes_with_postgres_syntax() {
|
||||
let mut old_fk = foreign_key("orders_user_id_fkey", "user_id", "users", "id");
|
||||
|
|
|
|||
Loading…
Reference in New Issue