Compare commits

...
Author SHA1 Message Date
arookieofc 4c3f106909 test(i18n): allowlist StarRocks product name in text catalog 2026-11-10 14:04:07 +08:00
arookieofc 391229f42c fix(db): connect to StarRocks via a MySQL-compatible preset (#2452)
StarRocks speaks the MySQL wire protocol but rejects the non-constant
SET sql_mode=(SELECT ...) handshake sqlx sends by default. Add a
StarRocks data source preset (MySQL driver, port 9030) that turns off
pipes_as_concat/no_engine_substitution, and recognize the starrocks URL
scheme.
2026-11-10 14:04:06 +08:00
5 changed files with 39 additions and 1 deletions

No files matched your search

+18
View File
@@ -594,6 +594,13 @@ fn is_identifier(name: &str) -> bool {
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
}
/// sqlx's handshake flags for `sql_mode`: with either enabled it sends
/// `SET sql_mode=(SELECT CONCAT(...))`, a non-constant subquery that MySQL-protocol
/// servers like StarRocks reject (#2452), so the StarRocks preset turns both off.
fn mysql_mode_flags(vendor: &str) -> (bool, bool) {
if vendor == "starrocks" { (false, false) } else { (true, true) }
}
/// A MySQL `SET` value: numbers and keywords as they are, anything else quoted.
fn mysql_value(v: &str) -> String {
let keyword = ["ON", "OFF", "TRUE", "FALSE", "DEFAULT"].iter().any(|k| k.eq_ignore_ascii_case(v));
@@ -839,6 +846,8 @@ impl Pool {
if let Some(c) = &setup.collation {
opts = opts.collation(c);
}
let (pipes_as_concat, no_engine_substitution) = mysql_mode_flags(&source.vendor);
opts = opts.pipes_as_concat(pipes_as_concat).no_engine_substitution(no_engine_substitution);
if let Some(n) = setup.statement_cache {
opts = opts.statement_cache_capacity(n);
}
@@ -1465,6 +1474,15 @@ mod tests {
assert!(Setup::of(&DataSource { properties: vec![prop("connect_timeout", "soon")], ..s }).is_err());
}
#[test]
fn starrocks_disables_the_non_constant_sql_mode_handshake() {
// MySQL and its other wire-compatible presets keep sqlx's defaults.
for vendor in ["", "mysql", "mariadb", "tidb"] {
assert_eq!(mysql_mode_flags(vendor), (true, true), "{vendor}");
}
assert_eq!(mysql_mode_flags("starrocks"), (false, false));
}
#[test]
fn sqlite_properties_become_pragmas() {
let s = DataSource { properties: vec![prop("cache_size", "-2000")], ..Default::default() };
+2 -1
View File
@@ -270,7 +270,7 @@ pub fn scheme_driver(scheme: &str) -> Option<DbDriver> {
match scheme.to_ascii_lowercase().as_str() {
"postgres" | "postgresql" | "pgsql" | "redshift" | "cockroachdb" | "cockroach" | "yugabytedb"
| "timescaledb" | "greenplum" => Some(DbDriver::Postgres),
"mysql" | "mariadb" | "tidb" | "singlestore" | "memsql" | "oceanbase" => Some(DbDriver::Mysql),
"mysql" | "mariadb" | "tidb" | "singlestore" | "memsql" | "oceanbase" | "starrocks" => Some(DbDriver::Mysql),
"sqlserver" | "mssql" | "mssqlserver" => Some(DbDriver::SqlServer),
"sqlite" | "sqlite3" => Some(DbDriver::Sqlite),
"clickhouse" | "http" | "https" => Some(DbDriver::Clickhouse),
@@ -559,6 +559,7 @@ mod tests {
assert_eq!((u.host.as_str(), u.port, u.user.as_str()), ("::1", None, "root"));
assert_eq!(parse_url("jdbc:redshift://r:5439/dev").unwrap().driver, Some(DbDriver::Postgres));
assert_eq!(parse_url("tidb://h:4000").unwrap().driver, Some(DbDriver::Mysql));
assert_eq!(parse_url("starrocks://h:9030").unwrap().driver, Some(DbDriver::Mysql));
assert_eq!(parse_url("sqlite:/tmp/a.db").unwrap().file.as_deref(), Some("/tmp/a.db"));
let u = parse_url("oracle://scott:[email protected]:1521/ORCLPDB1").unwrap();
assert_eq!(u.driver, Some(DbDriver::Oracle));
+8
View File
@@ -212,6 +212,14 @@ describe("data source types", () => {
expect(labels).toEqual([...labels].sort((a, b) => a.localeCompare(b)));
});
it("adds StarRocks on the MySQL driver with the 9030 preset (#2452)", () => {
expect(vendorPreset(vendorById("starrocks")!)).toEqual({ driver: "mysql", vendor: "starrocks", port: 9030 });
expect(sourceUrl({ driver: "mysql", vendor: "starrocks", host: "sr", port: 9030, database: "" })).toBe(
"jdbc:starrocks://sr:9030",
);
expect(parseSourceUrl("starrocks://[email protected]:9030/test")?.source.vendor).toBe("starrocks");
});
it("falls back to the driver for unknown or mismatched presets", () => {
expect(vendorOf({ driver: "mysql", vendor: "tidb" }).label).toBe("TiDB");
expect(vendorOf({ driver: "postgres", vendor: "tidb" }).id).toBe("postgres");
+10
View File
@@ -75,6 +75,15 @@ export const VENDORS: readonly DbVendor[] = [
scheme: "singlestore",
},
{ id: "sqlite", label: "SQLite", driver: "sqlite", port: null, icon: "db-sqlite", scheme: "sqlite" },
{
id: "starrocks",
label: "StarRocks",
driver: "mysql",
port: 9030,
// No dedicated glyph yet: StarRocks speaks the MySQL protocol, so reuse its icon.
icon: "db-mysql",
scheme: "starrocks",
},
{
id: "supabase",
label: "Supabase",
@@ -138,6 +147,7 @@ const SCHEMES: Record<string, string> = {
singlestore: "singlestore",
memsql: "singlestore",
oceanbase: "oceanbase",
starrocks: "starrocks",
oracle: "oracle",
oracledb: "oracle",
sqlserver: "sqlServer",
+1
View File
@@ -188,6 +188,7 @@ const UNTRANSLATED = new Set<string>([
"Rustfmt",
"SingleStore",
"SQLite",
"StarRocks",
"Supabase",
"TiDB",
"TimescaleDB",