Skip to content

Commit f2fea27

Browse files
authored
Fix encoding and decoding of MySQL enums in sqlx::Type (#3371)
1 parent 7cfbb24 commit f2fea27

File tree

6 files changed

+317
-10
lines changed

6 files changed

+317
-10
lines changed

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,11 @@ name = "mysql-describe"
287287
path = "tests/mysql/describe.rs"
288288
required-features = ["mysql"]
289289

290+
[[test]]
291+
name = "mysql-derives"
292+
path = "tests/mysql/derives.rs"
293+
required-features = ["mysql", "derive"]
294+
290295
[[test]]
291296
name = "mysql-macros"
292297
path = "tests/mysql/macros.rs"

sqlx-macros-core/src/derives/type.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,6 @@ fn expand_derive_has_sql_type_strong_enum(
183183
fn type_info() -> ::sqlx::mysql::MySqlTypeInfo {
184184
::sqlx::mysql::MySqlTypeInfo::__enum()
185185
}
186-
187-
fn compatible(ty: &::sqlx::mysql::MySqlTypeInfo) -> ::std::primitive::bool {
188-
*ty == ::sqlx::mysql::MySqlTypeInfo::__enum()
189-
}
190186
}
191187
));
192188
}

sqlx-mysql/src/type_info.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,17 @@ impl MySqlTypeInfo {
2727

2828
#[doc(hidden)]
2929
pub const fn __enum() -> Self {
30+
// Newer versions of MySQL seem to expect that a parameter binding of `MYSQL_TYPE_ENUM`
31+
// means that the value is encoded as an integer.
32+
//
33+
// For "strong" enums inputted as strings, we need to specify this type instead
34+
// for wider compatibility. This works on all covered versions of MySQL and MariaDB.
35+
//
36+
// Annoyingly, MySQL's developer documentation doesn't really explain this anywhere;
37+
// this had to be determined experimentally.
3038
Self {
31-
r#type: ColumnType::Enum,
32-
flags: ColumnFlags::BINARY,
39+
r#type: ColumnType::String,
40+
flags: ColumnFlags::ENUM,
3341
max_size: None,
3442
}
3543
}

sqlx-test/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,3 @@ sqlx = { default-features = false, path = ".." }
99
env_logger = "0.11"
1010
dotenvy = "0.15.0"
1111
anyhow = "1.0.26"
12-
async-std = { version = "1.8.0", features = [ "attributes" ] }
13-
tokio = { version = "1.0.1", features = [ "full" ] }

tests/mysql/derives.rs

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
use sqlx_mysql::MySql;
2+
use sqlx_test::new;
3+
4+
#[sqlx::test]
5+
async fn test_derive_strong_enum() -> anyhow::Result<()> {
6+
#[derive(sqlx::Type, PartialEq, Eq, Debug)]
7+
#[sqlx(rename_all = "PascalCase")]
8+
enum PascalCaseEnum {
9+
FooFoo,
10+
BarBar,
11+
BazBaz,
12+
}
13+
14+
#[derive(sqlx::Type, PartialEq, Eq, Debug)]
15+
#[sqlx(rename_all = "camelCase")]
16+
enum CamelCaseEnum {
17+
FooFoo,
18+
BarBar,
19+
BazBaz,
20+
}
21+
22+
#[derive(sqlx::Type, PartialEq, Eq, Debug)]
23+
#[sqlx(rename_all = "snake_case")]
24+
enum SnakeCaseEnum {
25+
FooFoo,
26+
BarBar,
27+
BazBaz,
28+
}
29+
30+
#[derive(sqlx::Type, PartialEq, Eq, Debug)]
31+
#[sqlx(rename_all = "SCREAMING_SNAKE_CASE")]
32+
enum ScreamingSnakeCaseEnum {
33+
FooFoo,
34+
BarBar,
35+
BazBaz,
36+
}
37+
38+
#[derive(sqlx::Type, PartialEq, Eq, Debug)]
39+
#[sqlx(rename_all = "kebab-case")]
40+
enum KebabCaseEnum {
41+
FooFoo,
42+
BarBar,
43+
BazBaz,
44+
}
45+
46+
#[derive(sqlx::Type, PartialEq, Eq, Debug)]
47+
#[sqlx(rename_all = "lowercase")]
48+
enum LowerCaseEnum {
49+
FooFoo,
50+
BarBar,
51+
BazBaz,
52+
}
53+
54+
#[derive(sqlx::Type, PartialEq, Eq, Debug)]
55+
#[sqlx(rename_all = "UPPERCASE")]
56+
enum UpperCaseEnum {
57+
FooFoo,
58+
BarBar,
59+
BazBaz,
60+
}
61+
62+
#[derive(sqlx::Type, PartialEq, Eq, Debug)]
63+
enum DefaultCaseEnum {
64+
FooFoo,
65+
BarBar,
66+
BazBaz,
67+
}
68+
69+
#[derive(sqlx::FromRow, PartialEq, Eq, Debug)]
70+
struct StrongEnumRow {
71+
pascal_case: PascalCaseEnum,
72+
camel_case: CamelCaseEnum,
73+
snake_case: SnakeCaseEnum,
74+
screaming_snake_case: ScreamingSnakeCaseEnum,
75+
kebab_case: KebabCaseEnum,
76+
lowercase: LowerCaseEnum,
77+
uppercase: UpperCaseEnum,
78+
default_case: DefaultCaseEnum,
79+
}
80+
81+
let mut conn = new::<MySql>().await?;
82+
83+
sqlx::raw_sql(
84+
r#"
85+
CREATE TEMPORARY TABLE strong_enum (
86+
pascal_case ENUM('FooFoo', 'BarBar', 'BazBaz'),
87+
camel_case ENUM('fooFoo', 'barBar', 'bazBaz'),
88+
snake_case ENUM('foo_foo', 'bar_bar', 'baz_baz'),
89+
screaming_snake_case ENUM('FOO_FOO', 'BAR_BAR', 'BAZ_BAZ'),
90+
kebab_case ENUM('foo-foo', 'bar-bar', 'baz-baz'),
91+
lowercase ENUM('foofoo', 'barbar', 'bazbaz'),
92+
uppercase ENUM('FOOFOO', 'BARBAR', 'BAZBAZ'),
93+
default_case ENUM('FooFoo', 'BarBar', 'BazBaz')
94+
);
95+
"#,
96+
)
97+
.execute(&mut conn)
98+
.await?;
99+
100+
let input = StrongEnumRow {
101+
pascal_case: PascalCaseEnum::FooFoo,
102+
camel_case: CamelCaseEnum::BarBar,
103+
snake_case: SnakeCaseEnum::BazBaz,
104+
screaming_snake_case: ScreamingSnakeCaseEnum::FooFoo,
105+
kebab_case: KebabCaseEnum::BarBar,
106+
lowercase: LowerCaseEnum::BazBaz,
107+
uppercase: UpperCaseEnum::FooFoo,
108+
default_case: DefaultCaseEnum::BarBar,
109+
};
110+
111+
sqlx::query(
112+
r#"
113+
INSERT INTO strong_enum(
114+
pascal_case,
115+
camel_case,
116+
snake_case,
117+
screaming_snake_case,
118+
kebab_case,
119+
lowercase,
120+
uppercase,
121+
default_case
122+
)
123+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
124+
"#,
125+
)
126+
.bind(&input.pascal_case)
127+
.bind(&input.camel_case)
128+
.bind(&input.snake_case)
129+
.bind(&input.screaming_snake_case)
130+
.bind(&input.kebab_case)
131+
.bind(&input.lowercase)
132+
.bind(&input.uppercase)
133+
.bind(&input.default_case)
134+
.execute(&mut conn)
135+
.await?;
136+
137+
let output: StrongEnumRow = sqlx::query_as("SELECT * FROM strong_enum")
138+
.fetch_one(&mut conn)
139+
.await?;
140+
141+
assert_eq!(input, output);
142+
143+
Ok(())
144+
}
145+
146+
#[sqlx::test]
147+
async fn test_derive_weak_enum() -> anyhow::Result<()> {
148+
#[derive(sqlx::Type, Debug, PartialEq, Eq)]
149+
#[repr(i8)]
150+
enum WeakEnumI8 {
151+
Foo = i8::MIN,
152+
Bar = 0,
153+
Baz = i8::MAX,
154+
}
155+
156+
#[derive(sqlx::Type, Debug, PartialEq, Eq)]
157+
#[repr(i16)]
158+
enum WeakEnumI16 {
159+
Foo = i16::MIN,
160+
Bar = 0,
161+
Baz = i16::MAX,
162+
}
163+
164+
#[derive(sqlx::Type, Debug, PartialEq, Eq)]
165+
#[repr(i32)]
166+
enum WeakEnumI32 {
167+
Foo = i32::MIN,
168+
Bar = 0,
169+
Baz = i32::MAX,
170+
}
171+
172+
#[derive(sqlx::Type, Debug, PartialEq, Eq)]
173+
#[repr(i64)]
174+
enum WeakEnumI64 {
175+
Foo = i64::MIN,
176+
Bar = 0,
177+
Baz = i64::MAX,
178+
}
179+
180+
#[derive(sqlx::Type, Debug, PartialEq, Eq)]
181+
#[repr(u8)]
182+
enum WeakEnumU8 {
183+
Foo = 0,
184+
Bar = 1,
185+
Baz = u8::MAX,
186+
}
187+
188+
#[derive(sqlx::Type, Debug, PartialEq, Eq)]
189+
#[repr(u16)]
190+
enum WeakEnumU16 {
191+
Foo = 0,
192+
Bar = 1,
193+
Baz = u16::MAX,
194+
}
195+
196+
#[derive(sqlx::Type, Debug, PartialEq, Eq)]
197+
#[repr(u32)]
198+
enum WeakEnumU32 {
199+
Foo = 0,
200+
Bar = 1,
201+
Baz = u32::MAX,
202+
}
203+
204+
#[derive(sqlx::Type, Debug, PartialEq, Eq)]
205+
#[repr(u64)]
206+
enum WeakEnumU64 {
207+
Foo = 0,
208+
Bar = 1,
209+
Baz = u64::MAX,
210+
}
211+
212+
#[derive(sqlx::FromRow, Debug, PartialEq, Eq)]
213+
struct WeakEnumRow {
214+
i8: WeakEnumI8,
215+
i16: WeakEnumI16,
216+
i32: WeakEnumI32,
217+
i64: WeakEnumI64,
218+
u8: WeakEnumU8,
219+
u16: WeakEnumU16,
220+
u32: WeakEnumU32,
221+
u64: WeakEnumU64,
222+
}
223+
224+
let mut conn = new::<MySql>().await?;
225+
226+
sqlx::raw_sql(
227+
r#"
228+
CREATE TEMPORARY TABLE weak_enum (
229+
i8 TINYINT,
230+
i16 SMALLINT,
231+
i32 INT,
232+
i64 BIGINT,
233+
u8 TINYINT UNSIGNED,
234+
u16 SMALLINT UNSIGNED,
235+
u32 INT UNSIGNED,
236+
u64 BIGINT UNSIGNED
237+
)
238+
"#,
239+
)
240+
.execute(&mut conn)
241+
.await?;
242+
243+
let rows_in = vec![
244+
WeakEnumRow {
245+
i8: WeakEnumI8::Foo,
246+
i16: WeakEnumI16::Foo,
247+
i32: WeakEnumI32::Foo,
248+
i64: WeakEnumI64::Foo,
249+
u8: WeakEnumU8::Foo,
250+
u16: WeakEnumU16::Foo,
251+
u32: WeakEnumU32::Foo,
252+
u64: WeakEnumU64::Foo,
253+
},
254+
WeakEnumRow {
255+
i8: WeakEnumI8::Bar,
256+
i16: WeakEnumI16::Bar,
257+
i32: WeakEnumI32::Bar,
258+
i64: WeakEnumI64::Bar,
259+
u8: WeakEnumU8::Bar,
260+
u16: WeakEnumU16::Bar,
261+
u32: WeakEnumU32::Bar,
262+
u64: WeakEnumU64::Bar,
263+
},
264+
WeakEnumRow {
265+
i8: WeakEnumI8::Baz,
266+
i16: WeakEnumI16::Baz,
267+
i32: WeakEnumI32::Baz,
268+
i64: WeakEnumI64::Baz,
269+
u8: WeakEnumU8::Baz,
270+
u16: WeakEnumU16::Baz,
271+
u32: WeakEnumU32::Baz,
272+
u64: WeakEnumU64::Baz,
273+
},
274+
];
275+
276+
for row in &rows_in {
277+
sqlx::query(
278+
r#"
279+
INSERT INTO weak_enum(i8, i16, i32, i64, u8, u16, u32, u64)
280+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
281+
"#,
282+
)
283+
.bind(&row.i8)
284+
.bind(&row.i16)
285+
.bind(&row.i32)
286+
.bind(&row.i64)
287+
.bind(&row.u8)
288+
.bind(&row.u16)
289+
.bind(&row.u32)
290+
.bind(&row.u64)
291+
.execute(&mut conn)
292+
.await?;
293+
}
294+
295+
let rows_out: Vec<WeakEnumRow> = sqlx::query_as("SELECT * FROM weak_enum")
296+
.fetch_all(&mut conn)
297+
.await?;
298+
299+
assert_eq!(rows_in, rows_out);
300+
301+
Ok(())
302+
}

0 commit comments

Comments
 (0)