Skip to content

Commit 5b107d3

Browse files
polazdimsssss
authored andcommitted
fix: cast BigInt values to text in JSON aggregation for MySQL/Cockroach (prisma#5752)
Closes prisma#5751. ## Summary - Cast MySQL BigInt/UnsignedBigInt values to CHAR in JSON_OBJECT to preserve precision in JS JSON parsing. - Extend Postgres visitor to also cast Cockroach INT8/Int8 to text in JSONB_BUILD_OBJECT. - Add visitor tests for both cases.
1 parent 2c976a4 commit 5b107d3

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

quaint/src/visitor/mysql.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ impl<'a> Mysql<'a> {
121121
})?;
122122
Ok(())
123123
}
124+
// Convert BigInt to string to preserve precision when parsed by JavaScript.
125+
(Some(TypeFamily::Int), Some("BIGINT" | "UNSIGNEDBIGINT")) => {
126+
self.write("CONVERT")?;
127+
self.surround_with("(", ")", |s| {
128+
s.visit_expression(expr)?;
129+
s.write(", ")?;
130+
s.write("CHAR")
131+
})?;
132+
Ok(())
133+
}
124134
_ => self.visit_expression(expr),
125135
},
126136
_ => self.visit_expression(expr),
@@ -717,6 +727,7 @@ fn get_target_table<'a>(query: &Query<'a>) -> Option<Table<'a>> {
717727

718728
#[cfg(test)]
719729
mod tests {
730+
use crate::ast::*;
720731
use crate::visitor::*;
721732

722733
fn expected_values<'a, T>(sql: &'static str, params: Vec<T>) -> (String, Vec<Value<'a>>)
@@ -816,6 +827,36 @@ mod tests {
816827
);
817828
}
818829

830+
#[test]
831+
fn json_build_object_casts_bigint_to_string() {
832+
let build_json = json_build_object(vec![(
833+
"id".into(),
834+
Column::from("id")
835+
.native_column_type(Some("BIGINT"))
836+
.type_family(TypeFamily::Int)
837+
.into(),
838+
)]);
839+
let query = Select::default().value(build_json);
840+
let (sql, _) = Mysql::build(query).unwrap();
841+
842+
assert_eq!("SELECT JSON_OBJECT('id', CONVERT(`id`, CHAR))", sql);
843+
}
844+
845+
#[test]
846+
fn json_build_object_casts_unsigned_bigint_to_string() {
847+
let build_json = json_build_object(vec![(
848+
"id".into(),
849+
Column::from("id")
850+
.native_column_type(Some("UNSIGNEDBIGINT"))
851+
.type_family(TypeFamily::Int)
852+
.into(),
853+
)]);
854+
let query = Select::default().value(build_json);
855+
let (sql, _) = Mysql::build(query).unwrap();
856+
857+
assert_eq!("SELECT JSON_OBJECT('id', CONVERT(`id`, CHAR))", sql);
858+
}
859+
819860
#[test]
820861
fn equality_with_a_json_value() {
821862
let expected = expected_values(

quaint/src/visitor/postgres.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl<'a> Postgres<'a> {
3333
}
3434
// Cast BigInt to text to preserve precision when parsed by JavaScript.
3535
// JavaScript's JSON.parse loses precision for integers > 2^53-1.
36-
(Some(TypeFamily::Int), Some("BIGINT")) => {
36+
(Some(TypeFamily::Int), Some("BIGINT" | "INT8")) => {
3737
self.visit_expression(expr)?;
3838
self.write("::text")?;
3939

@@ -1425,6 +1425,20 @@ mod tests {
14251425
assert_eq!(sql, "SELECT JSONB_BUILD_OBJECT('id', \"id\"::text)");
14261426
}
14271427

1428+
#[test]
1429+
fn int8() {
1430+
let build_json = json_build_object(vec![(
1431+
"id".into(),
1432+
Column::from("id")
1433+
.native_column_type(Some("INT8"))
1434+
.type_family(TypeFamily::Int)
1435+
.into(),
1436+
)]);
1437+
let query = Select::default().value(build_json);
1438+
let (sql, _) = Postgres::build(query).unwrap();
1439+
1440+
assert_eq!(sql, "SELECT JSONB_BUILD_OBJECT('id', \"id\"::text)");
1441+
}
14281442
fn build_json_object(num_fields: u32) -> JsonBuildObject<'static> {
14291443
let fields = (1..=num_fields)
14301444
.map(|i| (format!("f{i}").into(), Expression::from(i as i64)))

0 commit comments

Comments
 (0)