fix(ci): split checks and enforce warning-free Rust builds
This commit is contained in:
1 parent
dcbb1c69c5
commit
083ffe7a35
44 files changed
+363
-324
No files matched your search
@@ -3139,9 +3139,9 @@ fn clone_pool_kind(pool: &PoolKind) -> PoolKind {
|
||||
#[cfg(feature = "duckdb-bundled")]
|
||||
PoolKind::DuckDbWorker(client) => PoolKind::DuckDbWorker(client.clone()),
|
||||
#[cfg(not(feature = "duckdb-bundled"))]
|
||||
PoolKind::DuckDb(con) => PoolKind::DuckDb(con.clone()),
|
||||
PoolKind::DuckDb(_) => PoolKind::DuckDb(()),
|
||||
#[cfg(not(feature = "duckdb-bundled"))]
|
||||
PoolKind::DuckDbWorker(client) => PoolKind::DuckDbWorker(client.clone()),
|
||||
PoolKind::DuckDbWorker(_) => PoolKind::DuckDbWorker(()),
|
||||
PoolKind::MongoDb(client) => PoolKind::MongoDb(client.clone()),
|
||||
PoolKind::ClickHouse(client) => PoolKind::ClickHouse(client.clone()),
|
||||
PoolKind::SqlServer(client) => PoolKind::SqlServer(client.clone()),
|
||||
@@ -3149,7 +3149,10 @@ fn clone_pool_kind(pool: &PoolKind) -> PoolKind {
|
||||
PoolKind::VectorDb(client) => PoolKind::VectorDb(client.clone()),
|
||||
PoolKind::InfluxDb(client) => PoolKind::InfluxDb(client.clone()),
|
||||
PoolKind::Agent(client) => PoolKind::Agent(client.clone()),
|
||||
#[cfg(feature = "duckdb-bundled")]
|
||||
PoolKind::ExternalTabular(ext) => PoolKind::ExternalTabular(ext.clone()),
|
||||
#[cfg(not(feature = "duckdb-bundled"))]
|
||||
PoolKind::ExternalTabular(_) => PoolKind::ExternalTabular(()),
|
||||
PoolKind::ExternalDriver { driver_id, config, session } => {
|
||||
PoolKind::ExternalDriver { driver_id: driver_id.clone(), config: config.clone(), session: session.clone() }
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ impl DuckDbConnection {
|
||||
self.draining.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
// Preserve PoisonError ownership so close_connection can still release the contained DuckDB handle.
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn into_inner(self) -> std::sync::LockResult<duckdb::Connection> {
|
||||
self.connection.into_inner()
|
||||
}
|
||||
|
||||
@@ -183,13 +183,15 @@ fn find_as_keyword_outside_quotes(sql: &str) -> Option<usize> {
|
||||
}
|
||||
in_double = !in_double;
|
||||
}
|
||||
b'a' | b'A' if !in_single && !in_double && i + 1 < bytes.len() => {
|
||||
if (bytes[i + 1] == b's' || bytes[i + 1] == b'S')
|
||||
b'a' | b'A'
|
||||
if !in_single
|
||||
&& !in_double
|
||||
&& i + 1 < bytes.len()
|
||||
&& (bytes[i + 1] == b's' || bytes[i + 1] == b'S')
|
||||
&& is_sql_word_boundary(bytes.get(i.wrapping_sub(1)).copied())
|
||||
&& is_sql_word_boundary(bytes.get(i + 2).copied())
|
||||
{
|
||||
return Some(i);
|
||||
}
|
||||
&& is_sql_word_boundary(bytes.get(i + 2).copied()) =>
|
||||
{
|
||||
return Some(i);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -513,7 +513,8 @@ impl DuckDbWorkerClient {
|
||||
let mut pending = self.inner.pending.lock().await;
|
||||
let ids = pending
|
||||
.iter()
|
||||
.filter_map(|(id, request)| (request.generation == generation).then(|| id.clone()))
|
||||
.filter(|&(_id, request)| request.generation == generation)
|
||||
.map(|(id, _request)| id.clone())
|
||||
.collect::<Vec<_>>();
|
||||
ids.into_iter().filter_map(|id| pending.remove(&id).map(|request| (id, request.sender))).collect::<Vec<_>>()
|
||||
};
|
||||
@@ -631,7 +632,8 @@ fn spawn_stdout_reader(
|
||||
let mut pending = pending.lock().await;
|
||||
let ids = pending
|
||||
.iter()
|
||||
.filter_map(|(id, request)| (request.generation == generation).then(|| id.clone()))
|
||||
.filter(|&(_id, request)| request.generation == generation)
|
||||
.map(|(id, _request)| id.clone())
|
||||
.collect::<Vec<_>>();
|
||||
ids.into_iter().filter_map(|id| pending.remove(&id).map(|request| (id, request.sender))).collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
@@ -222,7 +222,7 @@ fn build_query_url(client: &InfluxdbClient, database: Option<&str>, sql: &str) -
|
||||
}
|
||||
let encoded_sql = utf8_percent_encode(sql, NON_ALPHANUMERIC);
|
||||
params.push(format!("q={encoded_sql}"));
|
||||
format!("{}/query?{}", &client.base_url, params.join("&").as_str())
|
||||
format!("{}/query?{}", client.base_url, params.join("&").as_str())
|
||||
}
|
||||
|
||||
fn encode_url_param(value: &str) -> String {
|
||||
@@ -231,12 +231,12 @@ fn encode_url_param(value: &str) -> String {
|
||||
|
||||
fn build_v2_buckets_url(client: &InfluxdbClient, offset: usize) -> Result<String, String> {
|
||||
let org = client.org.as_deref().ok_or_else(|| "InfluxDB 2.x organization is required".to_string())?;
|
||||
Ok(format!("{}/api/v2/buckets?org={}&limit=100&offset={offset}", &client.base_url, encode_url_param(org)))
|
||||
Ok(format!("{}/api/v2/buckets?org={}&limit=100&offset={offset}", client.base_url, encode_url_param(org)))
|
||||
}
|
||||
|
||||
fn build_v2_query_url(client: &InfluxdbClient) -> Result<String, String> {
|
||||
let org = client.org.as_deref().ok_or_else(|| "InfluxDB 2.x organization is required".to_string())?;
|
||||
Ok(format!("{}/api/v2/query?org={}", &client.base_url, encode_url_param(org)))
|
||||
Ok(format!("{}/api/v2/query?org={}", client.base_url, encode_url_param(org)))
|
||||
}
|
||||
|
||||
fn build_request(client: &InfluxdbClient, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
|
||||
@@ -1692,7 +1692,7 @@ fn parse_legacy_mongo_date_display(value: &str) -> Option<DateTime> {
|
||||
if !millis.chars().all(|ch| ch.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
DateTime::parse_rfc3339_str(format!("{date}T{seconds}.{}Z", format!("{millis:0<3}"))).ok()
|
||||
DateTime::parse_rfc3339_str(format!("{date}T{seconds}.{millis:0<3}Z")).ok()
|
||||
}
|
||||
|
||||
fn parse_extended_json_date(obj: &serde_json::Map<String, serde_json::Value>) -> Option<DateTime> {
|
||||
@@ -2003,7 +2003,7 @@ mod tests {
|
||||
#[test]
|
||||
fn document_id_filters_try_object_id_then_string_for_hex_ids() {
|
||||
let id = "507f1f77bcf86cd799439011";
|
||||
let filters = document_id_filters(&id);
|
||||
let filters = document_id_filters(id);
|
||||
|
||||
assert_eq!(filters.len(), 2);
|
||||
assert!(matches!(filters[0].get("_id"), Some(Bson::ObjectId(_))));
|
||||
@@ -2013,7 +2013,7 @@ mod tests {
|
||||
#[test]
|
||||
fn document_id_filters_use_string_only_for_non_hex_ids() {
|
||||
let id = "customer-42";
|
||||
let filters = document_id_filters(&id);
|
||||
let filters = document_id_filters(id);
|
||||
|
||||
assert_eq!(filters.len(), 1);
|
||||
assert!(matches!(filters[0].get("_id"), Some(Bson::String(value)) if value == id));
|
||||
|
||||
@@ -4547,7 +4547,6 @@ UNIQUE KEY(`tenant_id`, `name``part`)
|
||||
#[test]
|
||||
fn mysql_tcp_keepalive_uses_milliseconds_not_seconds() {
|
||||
assert_eq!(MYSQL_TCP_KEEPALIVE_MS, 30_000);
|
||||
assert!(MYSQL_TCP_KEEPALIVE_MS >= 1_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1400,7 +1400,7 @@ fn sqlite_completion_tables(
|
||||
if type_filters.is_empty() {
|
||||
type_filters.extend(["table", "view"]);
|
||||
}
|
||||
let placeholders = std::iter::repeat("?").take(type_filters.len()).collect::<Vec<_>>().join(", ");
|
||||
let placeholders = std::iter::repeat_n("?", type_filters.len()).collect::<Vec<_>>().join(", ");
|
||||
let sql = format!(
|
||||
"SELECT name, type FROM {}.sqlite_master WHERE type IN ({}) AND name NOT LIKE 'sqlite_%' AND {} ORDER BY name LIMIT ?",
|
||||
sqlite_quote_ident(&schema),
|
||||
|
||||
@@ -1315,7 +1315,7 @@ fn sqlserver_completion_assistant_sql(request: &crate::types::CompletionAssistan
|
||||
}
|
||||
|
||||
if queries.is_empty() {
|
||||
format!("SELECT TOP (0) CAST('' AS NVARCHAR(128)) AS name, CAST('' AS NVARCHAR(128)) AS schema_name, CAST('' AS NVARCHAR(60)) AS object_type, CAST(NULL AS NVARCHAR(128)) AS parent_schema, CAST(NULL AS NVARCHAR(128)) AS parent_name, CAST(NULL AS NVARCHAR(MAX)) AS object_comment, CAST(NULL AS NVARCHAR(128)) AS data_type")
|
||||
"SELECT TOP (0) CAST('' AS NVARCHAR(128)) AS name, CAST('' AS NVARCHAR(128)) AS schema_name, CAST('' AS NVARCHAR(60)) AS object_type, CAST(NULL AS NVARCHAR(128)) AS parent_schema, CAST(NULL AS NVARCHAR(128)) AS parent_name, CAST(NULL AS NVARCHAR(MAX)) AS object_comment, CAST(NULL AS NVARCHAR(128)) AS data_type".to_string()
|
||||
} else if queries.len() == 1 {
|
||||
format!("SELECT * FROM ({}) AS dbx_completion ORDER BY name", queries.remove(0))
|
||||
} else {
|
||||
|
||||
@@ -198,7 +198,7 @@ async fn list_milvus_databases(client: &VectorClient) -> Result<Vec<String>, Str
|
||||
if !names.iter().any(|name| name == "default") {
|
||||
names.push("default".to_string());
|
||||
}
|
||||
names.sort_by(|a, b| a.cmp(b));
|
||||
names.sort();
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ pub async fn list_databases_core(state: &AppState, connection_id: &str) -> Resul
|
||||
Err(error) => Err(error),
|
||||
},
|
||||
PoolKind::Elasticsearch(_) => Ok(vec!["default".to_string()]),
|
||||
PoolKind::VectorDb(client) => vector_driver::list_databases(&client).await,
|
||||
PoolKind::VectorDb(client) => vector_driver::list_databases(client).await,
|
||||
PoolKind::Agent(client) => {
|
||||
let mut client = client.lock().await;
|
||||
match client.mongo_list_databases::<Vec<serde_json::Value>>().await {
|
||||
@@ -216,7 +216,7 @@ pub async fn list_collections_core(
|
||||
.map(|n| CollectionInfo { name: n.clone(), id: n, dimension: None, kind: None, bucket_name: None })
|
||||
.collect())
|
||||
}
|
||||
PoolKind::VectorDb(client) => vector_driver::list_collections_with_db(&client, database).await,
|
||||
PoolKind::VectorDb(client) => vector_driver::list_collections_with_db(client, database).await,
|
||||
PoolKind::Agent(client) => {
|
||||
let mut client = client.lock().await;
|
||||
let names = sort_names(client.mongo_list_collections(database).await?);
|
||||
|
||||
@@ -161,7 +161,7 @@ impl MessageQueueAdmin for PulsarAdmin {
|
||||
|
||||
async fn list_tenants(&self) -> Result<Vec<TenantInfo>, String> {
|
||||
let names: Vec<String> = self.get_json(&self.profile.tenants_path()).await?;
|
||||
stream::iter(names.into_iter())
|
||||
stream::iter(names)
|
||||
.map(|name| async move {
|
||||
match self.get_tenant(&name).await {
|
||||
Ok(tenant) => Ok(tenant),
|
||||
@@ -214,7 +214,7 @@ impl MessageQueueAdmin for PulsarAdmin {
|
||||
async fn list_namespaces(&self, tenant: &str) -> Result<Vec<NamespaceInfo>, String> {
|
||||
// Returns fully-qualified `tenant/namespace` strings.
|
||||
let names: Vec<String> = self.get_json(&self.profile.namespaces_path(tenant)).await?;
|
||||
stream::iter(names.into_iter())
|
||||
stream::iter(names)
|
||||
.map(|full| async move {
|
||||
let namespace = full.rsplit('/').next().unwrap_or(&full).to_string();
|
||||
let admin_roles = match self.namespace_admin_roles(tenant, &namespace).await {
|
||||
@@ -282,7 +282,7 @@ impl MessageQueueAdmin for PulsarAdmin {
|
||||
};
|
||||
let partitioned_set: std::collections::HashSet<String> = partitioned.iter().cloned().collect();
|
||||
let domain = if persistent { "persistent" } else { "non-persistent" };
|
||||
let partitioned_topics = stream::iter(partitioned.into_iter())
|
||||
let partitioned_topics = stream::iter(partitioned)
|
||||
.map(|full| async move {
|
||||
let partitions = self.partition_count_for_topic(domain, &full).await?;
|
||||
Ok::<_, String>(TopicInfo {
|
||||
|
||||
@@ -16,22 +16,16 @@ use super::util::truncate;
|
||||
/// project's encrypted connection-secrets / keychain path and are never logged.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "camelCase")]
|
||||
#[derive(Default)]
|
||||
pub enum MqAuth {
|
||||
#[default]
|
||||
None,
|
||||
/// Pulsar JWT bearer token.
|
||||
Token {
|
||||
token: String,
|
||||
},
|
||||
Token { token: String },
|
||||
/// HTTP Basic auth.
|
||||
Basic {
|
||||
username: String,
|
||||
password: String,
|
||||
},
|
||||
Basic { username: String, password: String },
|
||||
/// Arbitrary API key header, e.g. `Authorization: <value>` or a custom header.
|
||||
ApiKey {
|
||||
header: String,
|
||||
value: String,
|
||||
},
|
||||
ApiKey { header: String, value: String },
|
||||
/// OAuth2 client-credentials flow (Pulsar's `oauth2` auth plugin).
|
||||
OAuth2 {
|
||||
issuer_url: String,
|
||||
@@ -44,12 +38,6 @@ pub enum MqAuth {
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for MqAuth {
|
||||
fn default() -> Self {
|
||||
MqAuth::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches an OAuth2 access token with its expiry so repeated requests reuse it.
|
||||
/// Also tracks in-flight token requests to prevent concurrent fetches.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
||||
@@ -31,6 +31,7 @@ impl MqSystemKind {
|
||||
/// features are hidden rather than failing at call time.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[derive(Default)]
|
||||
pub struct MqCapabilities {
|
||||
pub supports_tenants: bool,
|
||||
pub supports_namespaces: bool,
|
||||
@@ -54,31 +55,6 @@ pub struct MqCapabilities {
|
||||
pub supports_send_message: bool,
|
||||
}
|
||||
|
||||
impl Default for MqCapabilities {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
supports_tenants: false,
|
||||
supports_namespaces: false,
|
||||
supports_partitioned_topics: false,
|
||||
supports_subscriptions: false,
|
||||
supports_create_subscription: false,
|
||||
supports_reset_cursor: false,
|
||||
supports_skip_messages: false,
|
||||
supports_clear_backlog: false,
|
||||
supports_peek_messages: false,
|
||||
supports_expire_messages: false,
|
||||
supports_rate_limits: false,
|
||||
supports_backlog_quota: false,
|
||||
supports_retention: false,
|
||||
supports_permissions: false,
|
||||
supports_geo_replication: false,
|
||||
supports_token_management: false,
|
||||
supports_raw_admin_api: false,
|
||||
supports_send_message: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a connectivity test, including the detected server version and how
|
||||
/// it was determined (probe vs. fallback) so the UI can warn appropriately.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -4,15 +4,14 @@ use crate::models::connection::ConnectionConfig;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(tag = "kind", rename_all = "camelCase")]
|
||||
#[derive(Default)]
|
||||
pub enum NacosAuthConfig {
|
||||
#[default]
|
||||
None,
|
||||
UsernamePassword { username: String, password: String },
|
||||
}
|
||||
|
||||
impl Default for NacosAuthConfig {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
UsernamePassword {
|
||||
username: String,
|
||||
password: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
|
||||
@@ -1063,7 +1063,9 @@ fn push_optional(params: &mut Vec<(String, String)>, key: &str, value: Option<St
|
||||
}
|
||||
}
|
||||
|
||||
fn build_publish_forms(req: NacosConfigUpsert, namespace: String) -> (Vec<(String, String)>, Vec<(String, String)>) {
|
||||
type NacosForm = Vec<(String, String)>;
|
||||
|
||||
fn build_publish_forms(req: NacosConfigUpsert, namespace: String) -> (NacosForm, NacosForm) {
|
||||
let mut v3_form = vec![
|
||||
("dataId".to_string(), req.data_id.clone()),
|
||||
("groupName".to_string(), req.group.clone()),
|
||||
|
||||
@@ -25,9 +25,11 @@ use crate::nacos::port::NacosAdmin;
|
||||
pub use crate::nacos::config::{NacosAdminConfig as NacosConfig, NacosAuthConfig};
|
||||
pub use crate::nacos::types::*;
|
||||
|
||||
type NacosAdminEntry = (NacosAdminConfig, Arc<dyn NacosAdmin>);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NacosAdminRegistry {
|
||||
instances: RwLock<HashMap<String, (NacosAdminConfig, Arc<dyn NacosAdmin>)>>,
|
||||
instances: RwLock<HashMap<String, NacosAdminEntry>>,
|
||||
build_locks: RwLock<HashMap<String, Arc<Mutex<()>>>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ pub async fn nacos_raw_request_core(
|
||||
req: NacosRawRequest,
|
||||
) -> Result<NacosRawResponse, String> {
|
||||
crate::nacos::http::validate_raw_api_path(&req.path)?;
|
||||
if req.method.to_ascii_uppercase() != "GET" {
|
||||
if !req.method.eq_ignore_ascii_case("GET") {
|
||||
ensure_connection_writable(state, conn_id, "Run mutating Nacos raw request").await?;
|
||||
}
|
||||
let admin = get_admin(state, conn_id).await?;
|
||||
|
||||
@@ -445,7 +445,7 @@ fn informix_view_definition(schema: Option<&str>, name: &str, source: &str) -> (
|
||||
let view_name = captures.get(1).unwrap();
|
||||
let target_name = strip_informix_owner_qualifiers(view_name.as_str(), schema);
|
||||
let body = strip_informix_owner_qualifiers(&trimmed[view_name.end()..], schema);
|
||||
return (target_name.trim().to_string(), body);
|
||||
(target_name.trim().to_string(), body)
|
||||
} else {
|
||||
let body = strip_informix_owner_qualifiers(trimmed, schema);
|
||||
(informix_identifier(name), format!(" AS\n{body}"))
|
||||
@@ -579,7 +579,7 @@ fn informix_owner_qualifier_replacement<'a>(source: &'a str, start: usize, schem
|
||||
return None;
|
||||
}
|
||||
let ident_start = skip_sql_whitespace(source, dot + 1);
|
||||
read_informix_identifier_text(source, ident_start).map(|(ident_end, ident_text)| (ident_end, ident_text))
|
||||
read_informix_identifier_text(source, ident_start)
|
||||
}
|
||||
|
||||
fn sql_single_quoted_literal_end(source: &str, start: usize) -> Option<usize> {
|
||||
|
||||
@@ -407,7 +407,7 @@ fn append_sql_target_safety_text(chars: &[char], result: &mut SqlTargetSafetyTex
|
||||
continue;
|
||||
}
|
||||
if ch == '/' && next == Some('*') {
|
||||
if let Some((body, close_index)) = mysql_executable_comment_body(&chars, index) {
|
||||
if let Some((body, close_index)) = mysql_executable_comment_body(chars, index) {
|
||||
result.text.push(' ');
|
||||
let body_chars: Vec<char> = body.chars().collect();
|
||||
append_sql_target_safety_text(&body_chars, result);
|
||||
@@ -423,7 +423,7 @@ fn append_sql_target_safety_text(chars: &[char], result: &mut SqlTargetSafetyTex
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if let Some((tag, tag_len)) = dollar_quote_tag_at(&chars, index) {
|
||||
if let Some((tag, tag_len)) = dollar_quote_tag_at(chars, index) {
|
||||
index += tag_len;
|
||||
while index + tag_len <= chars.len() && !chars[index..index + tag_len].iter().collect::<String>().eq(&tag) {
|
||||
index += 1;
|
||||
@@ -433,7 +433,7 @@ fn append_sql_target_safety_text(chars: &[char], result: &mut SqlTargetSafetyTex
|
||||
continue;
|
||||
}
|
||||
if ch == '\'' {
|
||||
index = skip_string_literal(&chars, index, '\'', '\'');
|
||||
index = skip_string_literal(chars, index, '\'', '\'');
|
||||
result.text.push(' ');
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -12,21 +12,16 @@ pub struct RunningQueryDiagnostics {
|
||||
|
||||
type InterruptFn = Box<dyn Fn() + Send + 'static>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum RunningTaskKind {
|
||||
Query,
|
||||
Count,
|
||||
Explain,
|
||||
Export,
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Default for RunningTaskKind {
|
||||
fn default() -> Self {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct RunningTaskMetadata {
|
||||
pub kind: RunningTaskKind,
|
||||
|
||||
@@ -158,7 +158,7 @@ fn effective_row_limit(format: &str, request: &QueryResultExportRequest) -> Opti
|
||||
}
|
||||
|
||||
fn xlsx_hard_limit_active(format: &str, request: &QueryResultExportRequest) -> bool {
|
||||
format == "xlsx" && request.row_limit.map_or(true, |limit| limit > XLSX_MAX_DATA_ROWS)
|
||||
format == "xlsx" && request.row_limit.is_none_or(|limit| limit > XLSX_MAX_DATA_ROWS)
|
||||
}
|
||||
|
||||
fn format_text_export_header(format: &str, columns: &[String]) -> String {
|
||||
|
||||
@@ -963,7 +963,7 @@ fn add_standard_limit(
|
||||
offset: usize,
|
||||
dedup_projection_count: Option<usize>,
|
||||
) -> String {
|
||||
let order_sql = dedup_projection_count.map_or(String::new(), |count| format_positional_order_by(count));
|
||||
let order_sql = dedup_projection_count.map_or(String::new(), format_positional_order_by);
|
||||
|
||||
if has_top_level_limit(statement) {
|
||||
if !order_sql.is_empty() {
|
||||
|
||||
@@ -774,12 +774,10 @@ fn command_may_target_first_key(argv: &[String]) -> bool {
|
||||
if argv.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
match argv[0].to_ascii_uppercase().as_str() {
|
||||
"PING" | "INFO" | "DBSIZE" | "TIME" | "ROLE" | "CLUSTER" | "CLIENT" | "COMMAND" | "HELLO" | "AUTH" | "QUIT" => {
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
!matches!(
|
||||
argv[0].to_ascii_uppercase().as_str(),
|
||||
"PING" | "INFO" | "DBSIZE" | "TIME" | "ROLE" | "CLUSTER" | "CLIENT" | "COMMAND" | "HELLO" | "AUTH" | "QUIT"
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn redis_load_more_in_db_core(
|
||||
|
||||
@@ -1611,7 +1611,7 @@ fn oracle_object_info_can_have_table_comment(object: &db::ObjectInfo) -> bool {
|
||||
}
|
||||
|
||||
fn oracle_type_is_table_or_view(value: &str) -> bool {
|
||||
let normalized = value.to_ascii_uppercase().replace(' ', "_").replace('-', "_");
|
||||
let normalized = value.to_ascii_uppercase().replace([' ', '-'], "_");
|
||||
matches!(normalized.as_str(), "TABLE" | "BASE_TABLE" | "VIEW")
|
||||
}
|
||||
|
||||
|
||||
@@ -395,9 +395,9 @@ impl SqlStatementSplitter {
|
||||
self.buffer.push(ch);
|
||||
}
|
||||
';' if !self.in_single_quote && !self.in_double_quote && !self.in_backtick => {
|
||||
if self.options.profile.supports_custom_delimiter_commands && self.on_delimiter_line() {
|
||||
self.buffer.push(ch);
|
||||
} else if self.custom_delimiter.is_some() {
|
||||
if (self.options.profile.supports_custom_delimiter_commands && self.on_delimiter_line())
|
||||
|| self.custom_delimiter.is_some()
|
||||
{
|
||||
self.buffer.push(ch);
|
||||
} else if self.options.profile.supports_mysql_routine_blocks
|
||||
&& starts_with_mysql_routine_block(&self.buffer)
|
||||
@@ -1567,6 +1567,7 @@ fn parse_insert_values_tail(tail: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SqlScanner {
|
||||
profile: SqlDialectProfile,
|
||||
in_single_quote: bool,
|
||||
@@ -1609,9 +1610,7 @@ impl SqlScanner {
|
||||
}
|
||||
|
||||
if !self.in_single_quote && !self.in_double_quote && !self.in_backtick {
|
||||
if ch == '-' && next == Some('-') {
|
||||
self.in_line_comment = true;
|
||||
} else if self.profile.supports_hash_line_comments && ch == '#' {
|
||||
if (ch == '-' && next == Some('-')) || (self.profile.supports_hash_line_comments && ch == '#') {
|
||||
self.in_line_comment = true;
|
||||
} else if ch == '/' && next == Some('*') {
|
||||
self.in_block_comment = true;
|
||||
@@ -1647,21 +1646,6 @@ impl SqlScanner {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SqlScanner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
profile: SqlDialectProfile::default(),
|
||||
in_single_quote: false,
|
||||
in_double_quote: false,
|
||||
in_backtick: false,
|
||||
in_line_comment: false,
|
||||
in_block_comment: false,
|
||||
dollar_quote_tag: None,
|
||||
previous: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn keyword_at(sql: &str, idx: usize, keyword: &str) -> bool {
|
||||
let end = idx + keyword.len();
|
||||
sql.get(idx..end).is_some_and(|candidate| candidate.eq_ignore_ascii_case(keyword))
|
||||
|
||||
@@ -158,25 +158,21 @@ fn redact_sensitive_assignments(sql: &str) -> String {
|
||||
while j < chars.len() && chars[j].is_whitespace() {
|
||||
j += 1;
|
||||
}
|
||||
if j < chars.len() && (chars[j] == '=' || chars[j] == ':') {
|
||||
if is_sensitive_key(&key) {
|
||||
out.push_str(&key);
|
||||
for k in i..j {
|
||||
out.push(chars[k]);
|
||||
}
|
||||
if j < chars.len() && (chars[j] == '=' || chars[j] == ':') && is_sensitive_key(&key) {
|
||||
out.push_str(&key);
|
||||
out.extend(chars[i..j].iter().copied());
|
||||
out.push(chars[j]);
|
||||
j += 1;
|
||||
while j < chars.len() && chars[j].is_whitespace() {
|
||||
out.push(chars[j]);
|
||||
j += 1;
|
||||
while j < chars.len() && chars[j].is_whitespace() {
|
||||
out.push(chars[j]);
|
||||
j += 1;
|
||||
}
|
||||
while j < chars.len() && !chars[j].is_whitespace() {
|
||||
j += 1;
|
||||
}
|
||||
out.push_str("[REDACTED]");
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
while j < chars.len() && !chars[j].is_whitespace() {
|
||||
j += 1;
|
||||
}
|
||||
out.push_str("[REDACTED]");
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
out.push_str(&key);
|
||||
}
|
||||
|
||||
@@ -1063,10 +1063,10 @@ fn merge_missing_tunnel_profile_secrets(profile: &mut TransportLayerConfig, prev
|
||||
current.password = previous.password.clone();
|
||||
}
|
||||
}
|
||||
(TransportLayerConfig::HttpTunnel(current), TransportLayerConfig::HttpTunnel(previous)) => {
|
||||
if current.token.is_empty() {
|
||||
current.token = previous.token.clone();
|
||||
}
|
||||
(TransportLayerConfig::HttpTunnel(current), TransportLayerConfig::HttpTunnel(previous))
|
||||
if current.token.is_empty() =>
|
||||
{
|
||||
current.token = previous.token.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -3246,7 +3246,7 @@ mod tests {
|
||||
let storage = Storage::open(&path).await.unwrap();
|
||||
|
||||
let original = mq_connection("pulsar", "existing-token");
|
||||
storage.save_connections(&[original.clone()]).await.unwrap();
|
||||
storage.save_connections(std::slice::from_ref(&original)).await.unwrap();
|
||||
|
||||
let mut metadata = original;
|
||||
metadata.name = "Pulsar renamed".to_string();
|
||||
|
||||
@@ -547,7 +547,7 @@ async fn try_export_native_table_stream(
|
||||
let row_csv = format_csv_rows(&[formatted]);
|
||||
write!(file, "\n{row_csv}").map_err(|e| format!("Failed to write CSV rows: {e}"))?;
|
||||
rows_exported += 1;
|
||||
if rows_exported % progress_interval == 0 {
|
||||
if rows_exported.is_multiple_of(progress_interval) {
|
||||
on_progress(TableExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
table_name: request.table_name.clone(),
|
||||
@@ -591,7 +591,7 @@ async fn try_export_native_table_stream(
|
||||
let row_tsv = format_tsv_rows(&[formatted]);
|
||||
write!(file, "\n{row_tsv}").map_err(|e| format!("Failed to write TXT rows: {e}"))?;
|
||||
rows_exported += 1;
|
||||
if rows_exported % progress_interval == 0 {
|
||||
if rows_exported.is_multiple_of(progress_interval) {
|
||||
on_progress(TableExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
table_name: request.table_name.clone(),
|
||||
@@ -636,7 +636,7 @@ async fn try_export_native_table_stream(
|
||||
);
|
||||
writer.write_row(&formatted).map_err(|e| format!("Failed to write XLSX row: {e}"))?;
|
||||
rows_exported += 1;
|
||||
if rows_exported % progress_interval == 0 {
|
||||
if rows_exported.is_multiple_of(progress_interval) {
|
||||
on_progress(TableExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
table_name: request.table_name.clone(),
|
||||
@@ -691,7 +691,7 @@ async fn try_export_native_table_stream(
|
||||
write_json_row_object(&mut file, col_names, &formatted)?;
|
||||
is_first_row = false;
|
||||
rows_exported += 1;
|
||||
if rows_exported % progress_interval == 0 {
|
||||
if rows_exported.is_multiple_of(progress_interval) {
|
||||
on_progress(TableExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
table_name: request.table_name.clone(),
|
||||
@@ -742,7 +742,7 @@ async fn try_export_native_table_stream(
|
||||
wrote_rows = true;
|
||||
}
|
||||
rows_exported += 1;
|
||||
if rows_exported % progress_interval == 0 {
|
||||
if rows_exported.is_multiple_of(progress_interval) {
|
||||
on_progress(TableExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
table_name: request.table_name.clone(),
|
||||
@@ -808,7 +808,7 @@ async fn try_export_native_table_stream(
|
||||
flush_pending(&mut file, &mut pending_rows)?;
|
||||
}
|
||||
rows_exported += 1;
|
||||
if rows_exported % progress_interval == 0 {
|
||||
if rows_exported.is_multiple_of(progress_interval) {
|
||||
on_progress(TableExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
table_name: request.table_name.clone(),
|
||||
@@ -839,7 +839,7 @@ async fn try_export_native_table_stream(
|
||||
match stream_result {
|
||||
Ok(false) => Ok(false),
|
||||
Ok(true) => {
|
||||
if rows_exported % progress_interval != 0 {
|
||||
if !rows_exported.is_multiple_of(progress_interval) {
|
||||
on_progress(TableExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
table_name: request.table_name.clone(),
|
||||
|
||||
@@ -2085,9 +2085,7 @@ where
|
||||
pending_rows.push(delimited_record_to_row(&record, columns.len(), config));
|
||||
}
|
||||
|
||||
let mut source_row_number = config.row_range.data_start_row;
|
||||
for record in reader.records() {
|
||||
source_row_number += 1;
|
||||
for (source_row_number, record) in (config.row_range.data_start_row.saturating_add(1)..).zip(reader.records()) {
|
||||
if config.row_range.last_data_row.is_some_and(|last| source_row_number > last) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -344,12 +344,7 @@ pub(crate) fn wrap_dameng_identity_insert_sql(insert_sql: &str, table: &str, sch
|
||||
|
||||
pub(crate) fn wrap_dameng_identity_insert_sql_for_table(insert_sql: &str, full_table: &str) -> String {
|
||||
let trimmed = insert_sql.trim().trim_end_matches(';').trim();
|
||||
format!(
|
||||
"{};\n{};\n{};",
|
||||
format!("SET IDENTITY_INSERT {full_table} ON"),
|
||||
trimmed,
|
||||
format!("SET IDENTITY_INSERT {full_table} OFF")
|
||||
)
|
||||
format!("SET IDENTITY_INSERT {full_table} ON;\n{trimmed};\nSET IDENTITY_INSERT {full_table} OFF;")
|
||||
}
|
||||
|
||||
async fn execute_transfer_write_statement(
|
||||
@@ -4139,7 +4134,7 @@ where
|
||||
can_reuse_source_table_ddl(source_db_type, target_db_type, preserves_target_table_name);
|
||||
let ddl = if can_reuse_source_ddl {
|
||||
let source_ddl = crate::schema::get_table_ddl_core(
|
||||
&state,
|
||||
state,
|
||||
&request.source_connection_id,
|
||||
&request.source_database,
|
||||
&request.source_schema,
|
||||
@@ -5222,19 +5217,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn transfer_create_table_result_treats_existing_table_as_preexisting() {
|
||||
assert_eq!(
|
||||
transfer_create_table_created(
|
||||
Err("ERROR: relation \"items\" already exists (SQLSTATE 42P07)".to_string()),
|
||||
"create"
|
||||
)
|
||||
.unwrap(),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
transfer_create_table_created(Err("错误: 关系 \"items\" 已经存在".to_string()), "create").unwrap(),
|
||||
false
|
||||
);
|
||||
assert_eq!(transfer_create_table_created(Ok(()), "create").unwrap(), true);
|
||||
assert!(!transfer_create_table_created(
|
||||
Err("ERROR: relation \"items\" already exists (SQLSTATE 42P07)".to_string()),
|
||||
"create"
|
||||
)
|
||||
.unwrap());
|
||||
assert!(!transfer_create_table_created(Err("错误: 关系 \"items\" 已经存在".to_string()), "create").unwrap());
|
||||
assert!(transfer_create_table_created(Ok(()), "create").unwrap());
|
||||
assert_eq!(
|
||||
transfer_create_table_created(Err("permission denied for schema public".to_string()), "create")
|
||||
.unwrap_err(),
|
||||
|
||||
Reference in new issue
Block a user