From acc14348ef5a6b7b900a83046812eddd2e7184fe Mon Sep 17 00:00:00 2001 From: Naoki Takahashi Date: Sun, 8 Feb 2026 18:05:32 +0900 Subject: [PATCH] fix(db): preserve DOUBLE type in SQLite export instead of converting to FLOAT SQLite's type mapper was converting both FLOAT and DOUBLE to REAL in SQL generation, then mapping REAL back to FLOAT during introspection, losing the DOUBLE distinction. Now DOUBLE is preserved as DOUBLE in generated SQL (SQLite accepts any type name with REAL affinity), and DOUBLE declared types are correctly parsed back to ColumnType::DOUBLE. Fixes #27 Co-Authored-By: Claude Opus 4.6 --- src/cli/tests/gen_sqlite_test.rs | 4 +-- .../adapters/type_mapping/sqlite_mapper.rs | 34 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/cli/tests/gen_sqlite_test.rs b/src/cli/tests/gen_sqlite_test.rs index bcafa9a..737866b 100644 --- a/src/cli/tests/gen_sqlite_test.rs +++ b/src/cli/tests/gen_sqlite_test.rs @@ -494,8 +494,8 @@ mod sqlite_sql_generator_tests { )); let sql = generator.generate_create_table(&table); - // SQLite では FLOAT も DOUBLE も REAL にマッピング - assert!(sql.contains(r#""latitude" REAL NOT NULL"#)); + // SQLite では DOUBLE はそのまま DOUBLE として出力(REAL アフィニティ) + assert!(sql.contains(r#""latitude" DOUBLE NOT NULL"#)); } #[test] diff --git a/src/db/src/adapters/type_mapping/sqlite_mapper.rs b/src/db/src/adapters/type_mapping/sqlite_mapper.rs index 61d4f0a..ea7d191 100644 --- a/src/db/src/adapters/type_mapping/sqlite_mapper.rs +++ b/src/db/src/adapters/type_mapping/sqlite_mapper.rs @@ -25,6 +25,8 @@ impl TypeMapper for SqliteTypeMapper { Some(ColumnType::VARCHAR { length: 255 }) } else if upper == "TEXT" { Some(ColumnType::TEXT) + } else if upper == "DOUBLE" { + Some(ColumnType::DOUBLE) } else if upper == "REAL" { Some(ColumnType::FLOAT) } else if upper == "BLOB" { @@ -47,7 +49,7 @@ impl TypeMapper for SqliteTypeMapper { ColumnType::JSONB => "TEXT".to_string(), ColumnType::DECIMAL { .. } => "TEXT".to_string(), ColumnType::FLOAT => "REAL".to_string(), - ColumnType::DOUBLE => "REAL".to_string(), + ColumnType::DOUBLE => "DOUBLE".to_string(), ColumnType::CHAR { .. } => "TEXT".to_string(), ColumnType::DATE => "TEXT".to_string(), ColumnType::TIME { .. } => "TEXT".to_string(), @@ -127,7 +129,7 @@ mod tests { fn test_sqlite_float_double() { let service = TypeMappingService::new(Dialect::SQLite); assert_eq!(service.to_sql_type(&ColumnType::FLOAT), "REAL"); - assert_eq!(service.to_sql_type(&ColumnType::DOUBLE), "REAL"); + assert_eq!(service.to_sql_type(&ColumnType::DOUBLE), "DOUBLE"); } #[test] @@ -166,6 +168,15 @@ mod tests { assert!(matches!(result, ColumnType::FLOAT)); } + #[test] + fn test_sqlite_parse_double() { + let service = TypeMappingService::new(Dialect::SQLite); + let metadata = TypeMetadata::default(); + + let result = service.from_sql_type("DOUBLE", &metadata).unwrap(); + assert!(matches!(result, ColumnType::DOUBLE)); + } + #[test] fn test_sqlite_parse_blob() { let service = TypeMappingService::new(Dialect::SQLite); @@ -174,4 +185,23 @@ mod tests { let result = service.from_sql_type("BLOB", &metadata).unwrap(); assert!(matches!(result, ColumnType::BLOB)); } + + /// Regression test for #27: DOUBLE round-trip through SQLite + #[test] + fn test_sqlite_double_round_trip() { + let service = TypeMappingService::new(Dialect::SQLite); + let metadata = TypeMetadata::default(); + + // DOUBLE → "DOUBLE" → ColumnType::DOUBLE + let sql_type = service.to_sql_type(&ColumnType::DOUBLE); + assert_eq!(sql_type, "DOUBLE"); + let parsed = service.from_sql_type(&sql_type, &metadata).unwrap(); + assert!(matches!(parsed, ColumnType::DOUBLE)); + + // FLOAT → "REAL" → ColumnType::FLOAT (existing behavior preserved) + let sql_type = service.to_sql_type(&ColumnType::FLOAT); + assert_eq!(sql_type, "REAL"); + let parsed = service.from_sql_type(&sql_type, &metadata).unwrap(); + assert!(matches!(parsed, ColumnType::FLOAT)); + } }