Skip to content

Commit 42f4096

Browse files
dmealingclaude
andcommitted
feat(python-migrate): R6 — field.float emits REAL; stringify REAL/DOUBLE on the wire
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 01d5eb5 commit 42f4096

4 files changed

Lines changed: 28 additions & 3 deletions

File tree

server/python/src/metaobjects/migrate/expected_schema.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
Json,
2626
Numeric,
2727
Real,
28+
Real4,
2829
SqlType,
2930
Text,
3031
Timestamp,
@@ -116,8 +117,10 @@ def _subtype_to_sql_type(field: MetaField) -> SqlType:
116117
return Integer(bits=32)
117118
if st in (fc.FIELD_SUBTYPE_LONG, fc.FIELD_SUBTYPE_CURRENCY):
118119
return Integer(bits=64)
119-
if st in (fc.FIELD_SUBTYPE_DOUBLE, fc.FIELD_SUBTYPE_FLOAT):
120+
if st == fc.FIELD_SUBTYPE_DOUBLE:
120121
return Real()
122+
if st == fc.FIELD_SUBTYPE_FLOAT:
123+
return Real4()
121124
if st == fc.FIELD_SUBTYPE_DECIMAL:
122125
return Numeric()
123126
if st == fc.FIELD_SUBTYPE_BOOLEAN:

server/python/src/metaobjects/migrate/postgres_emit.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Json,
1616
Numeric,
1717
Real,
18+
Real4,
1819
SqlType,
1920
Text,
2021
Timestamp,
@@ -125,6 +126,8 @@ def _pg_type(t: SqlType) -> str:
125126
return "BIGINT" if t.bits == 64 else "INTEGER"
126127
if isinstance(t, Real):
127128
return "DOUBLE PRECISION"
129+
if isinstance(t, Real4):
130+
return "REAL"
128131
if isinstance(t, Numeric):
129132
if t.precision is not None and t.scale is not None:
130133
return f"NUMERIC({t.precision},{t.scale})"

server/python/src/metaobjects/migrate/sql_type.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ class Real:
2424
kind: Literal["real"] = field(default="real", init=False)
2525

2626

27+
@dataclass(frozen=True)
28+
class Real4:
29+
kind: Literal["real4"] = field(default="real4", init=False)
30+
31+
2732
@dataclass(frozen=True)
2833
class Numeric:
2934
precision: int | None = None
@@ -62,4 +67,4 @@ class Uuid:
6267
kind: Literal["uuid"] = field(default="uuid", init=False)
6368

6469

65-
SqlType = Union[Text, Integer, Real, Numeric, Boolean, Timestamp, Date, Json, Blob, Uuid]
70+
SqlType = Union[Text, Integer, Real, Real4, Numeric, Boolean, Timestamp, Date, Json, Blob, Uuid]

server/python/tests/integration/normalization.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def normalize_value(v: Any) -> Any:
3131
# PKs as strings, so this gives stable cross-port output without overreach.
3232
return str(v) if v > 2**31 - 1 or v < -(2**31) else v
3333
if isinstance(v, float):
34-
return v
34+
return _canonical_float(v)
3535
if isinstance(v, decimal.Decimal):
3636
return _canonical_decimal(v)
3737
if isinstance(v, uuid.UUID):
@@ -83,6 +83,20 @@ def _canonical_decimal(d: decimal.Decimal) -> str:
8383
return s
8484

8585

86+
def _canonical_float(x: float) -> str:
87+
# In-band dyadic values (per normalization.md) render plain + shortest via repr().
88+
s = repr(x)
89+
if "e" in s or "E" in s:
90+
raise ValueError(
91+
f"_canonical_float: {x} is outside the plain-decimal band (exponential "
92+
"notation); REAL/DOUBLE fixture values must be in-band dyadic rationals — "
93+
"see fixtures/persistence-conformance/normalization.md"
94+
)
95+
if "." in s:
96+
s = s.rstrip("0").rstrip(".")
97+
return s
98+
99+
86100
def _ms_suffix(microseconds: int) -> str:
87101
if microseconds == 0:
88102
return ""

0 commit comments

Comments
 (0)