Skip to content

Commit 43ca649

Browse files
committed
fix(postgres): preserve aggregate and array cast arity
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
1 parent 059a7b2 commit 43ca649

5 files changed

Lines changed: 73 additions & 8 deletions

File tree

  • quaint/src/visitor
  • query-compiler/query-builders/sql-query-builder/src
  • query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/data_types/native
  • schema-engine
    • connectors/sql-schema-connector/src/flavour/postgres
    • sql-migration-tests/tests/migrations

quaint/src/visitor/postgres.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,24 @@ impl<'a> Postgres<'a> {
2727
match expr.kind() {
2828
ExpressionKind::Column(col) => match (col.type_family.as_ref(), col.native_type.as_deref()) {
2929
(Some(TypeFamily::Decimal(_)), Some("MONEY")) => {
30+
let is_list = col.is_list;
3031
self.visit_expression(expr)?;
3132
self.write("::numeric")?;
33+
if is_list {
34+
self.write("[]")?;
35+
}
3236

3337
Ok(())
3438
}
3539
// Cast BigInt to text to preserve precision when parsed by JavaScript.
3640
// JavaScript's JSON.parse loses precision for integers > 2^53-1.
3741
(Some(TypeFamily::Int), Some("BIGINT" | "INT8")) => {
42+
let is_list = col.is_list;
3843
self.visit_expression(expr)?;
3944
self.write("::text")?;
45+
if is_list {
46+
self.write("[]")?;
47+
}
4048

4149
Ok(())
4250
}
@@ -1442,6 +1450,22 @@ mod tests {
14421450
assert_eq!(sql, "SELECT JSONB_BUILD_OBJECT('money', \"money\"::numeric)");
14431451
}
14441452

1453+
#[test]
1454+
fn money_array() {
1455+
let build_json = json_build_object(vec![(
1456+
"money".into(),
1457+
Column::from("money")
1458+
.native_column_type(Some("money"))
1459+
.type_family(TypeFamily::Decimal(None))
1460+
.set_is_list(true)
1461+
.into(),
1462+
)]);
1463+
let query = Select::default().value(build_json);
1464+
let (sql, _) = Postgres::build(query).unwrap();
1465+
1466+
assert_eq!(sql, "SELECT JSONB_BUILD_OBJECT('money', \"money\"::numeric[])");
1467+
}
1468+
14451469
#[test]
14461470
fn bigint() {
14471471
let build_json = json_build_object(vec![(
@@ -1457,6 +1481,22 @@ mod tests {
14571481
assert_eq!(sql, "SELECT JSONB_BUILD_OBJECT('id', \"id\"::text)");
14581482
}
14591483

1484+
#[test]
1485+
fn bigint_array() {
1486+
let build_json = json_build_object(vec![(
1487+
"id".into(),
1488+
Column::from("id")
1489+
.native_column_type(Some("BigInt"))
1490+
.type_family(TypeFamily::Int)
1491+
.set_is_list(true)
1492+
.into(),
1493+
)]);
1494+
let query = Select::default().value(build_json);
1495+
let (sql, _) = Postgres::build(query).unwrap();
1496+
1497+
assert_eq!(sql, "SELECT JSONB_BUILD_OBJECT('id', \"id\"::text[])");
1498+
}
1499+
14601500
#[test]
14611501
fn int8() {
14621502
let build_json = json_build_object(vec![(

query-compiler/query-builders/sql-query-builder/src/read.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ fn apply_aggregate_selections(
242242
.field
243243
.as_scalar()
244244
.unwrap()
245-
.as_column_with_style(ctx, col_style))
245+
.as_column_with_style(ctx, col_style)
246+
.set_is_selected(true))
246247
.alias(next_field.db_alias().into_owned()),
247248
)
248249
}),
@@ -253,7 +254,8 @@ fn apply_aggregate_selections(
253254
.field
254255
.as_scalar()
255256
.unwrap()
256-
.as_column_with_style(ctx, col_style))
257+
.as_column_with_style(ctx, col_style)
258+
.set_is_selected(true))
257259
.alias(next_field.db_alias().into_owned()),
258260
)
259261
}),

query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/data_types/native/postgres.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,24 @@ mod postgres_money {
163163

164164
Ok(())
165165
}
166+
167+
#[connector_test(schema(schema_decimal))]
168+
async fn native_money_aggregates(runner: Runner) -> TestResult<()> {
169+
runner
170+
.raw_execute(
171+
r#"INSERT INTO "Table" ("id", "money", "moneyList") VALUES
172+
(1, '$10.25', ARRAY[]::money[]),
173+
(2, '$20.75', ARRAY[]::money[]);"#,
174+
)
175+
.await?;
176+
177+
insta::assert_snapshot!(
178+
run_query!(&runner, r#"{ aggregateTable { _sum { money } _avg { money } } }"#),
179+
@r###"{"data":{"aggregateTable":{"_sum":{"money":"31"},"_avg":{"money":"15.5"}}}}"###
180+
);
181+
182+
Ok(())
183+
}
166184
}
167185

168186
#[test_suite(only(Postgres))]

schema-engine/connectors/sql-schema-connector/src/flavour/postgres/renderer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ fn render_postgres_alter_enum(
974974
let sql = format!(
975975
"ALTER TABLE {table_name} \
976976
ALTER COLUMN {column_name} TYPE {tmp_name}{array} \
977-
USING ({column_name}::text::{tmp_name}{array})",
977+
USING ({column_name}::text{array}::{tmp_name}{array})",
978978
table_name = QuotedWithPrefix::pg_from_table_walker(column.table()),
979979
column_name = Quoted::postgres_ident(column.name()),
980980
array = array,

schema-engine/sql-migration-tests/tests/migrations/enums.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,8 @@ fn enum_array_modification_should_work(api: TestApi) {
542542
.send_sync()
543543
.assert_applied_migrations(&["01init"]);
544544

545+
api.raw_cmd(r#"INSERT INTO "Test" ("positions") VALUES (ARRAY['First', 'Second']::"Position"[])"#);
546+
545547
let dm = r#"
546548
datasource test {
547549
provider = "postgres"
@@ -623,8 +625,9 @@ fn alter_enum_and_change_default_must_work(api: TestApi) {
623625
provider = "postgres"
624626
}
625627
model Cat {
626-
id Int @id
627-
moods Mood[] @default([])
628+
id Int @id
629+
mood Mood
630+
moods Mood[] @default([])
628631
}
629632
enum Mood {
630633
SLEEPY
@@ -639,8 +642,9 @@ fn alter_enum_and_change_default_must_work(api: TestApi) {
639642
provider = "postgres"
640643
}
641644
model Cat {
642-
id Int @id
643-
moods Mood[] @default([SLEEPY])
645+
id Int @id
646+
mood Mood
647+
moods Mood[] @default([SLEEPY])
644648
}
645649
enum Mood {
646650
HUNGRY
@@ -682,7 +686,8 @@ fn alter_enum_and_change_default_must_work(api: TestApi) {
682686
BEGIN;
683687
CREATE TYPE "Mood_new" AS ENUM ('HUNGRY', 'SLEEPY');
684688
ALTER TABLE "public"."Cat" ALTER COLUMN "moods" DROP DEFAULT;
685-
ALTER TABLE "Cat" ALTER COLUMN "moods" TYPE "Mood_new"[] USING ("moods"::text::"Mood_new"[]);
689+
ALTER TABLE "Cat" ALTER COLUMN "mood" TYPE "Mood_new" USING ("mood"::text::"Mood_new");
690+
ALTER TABLE "Cat" ALTER COLUMN "moods" TYPE "Mood_new"[] USING ("moods"::text[]::"Mood_new"[]);
686691
ALTER TYPE "Mood" RENAME TO "Mood_old";
687692
ALTER TYPE "Mood_new" RENAME TO "Mood";
688693
DROP TYPE "public"."Mood_old";

0 commit comments

Comments
 (0)