fix(ci): stabilize cross-platform workflow checks

This commit is contained in:
zhefox
2026-07-29 21:55:43 +08:00
parent 37df5b93b1
commit f8000012f7
40 changed files with 251 additions and 139 deletions
@@ -651,7 +651,7 @@ SELECT SHA2(UUID(), 256), usage.api_key_id, ?, COUNT(*),
COALESCE(SUM(usage.cache_creation_input_tokens), 0),
COALESCE(SUM(usage.cache_read_input_tokens), 0),
COALESCE(SUM(usage.total_cost_usd), 0), MAX(api_keys.name), ?, ?
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN api_keys ON api_keys.id = usage.api_key_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
AND usage.api_key_id IS NOT NULL AND usage.api_key_id <> ''
@@ -736,7 +736,7 @@ SELECT SHA2(UUID(), 256), usage.user_id, ?, COUNT(*),
COALESCE(SUM(usage.cache_creation_input_tokens), 0),
COALESCE(SUM(usage.cache_read_input_tokens), 0),
COALESCE(SUM(usage.total_cost_usd), 0), MAX(users.username), ?, ?
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN users ON users.id = usage.user_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
AND usage.user_id IS NOT NULL AND usage.user_id <> ''
@@ -143,7 +143,7 @@ JOIN (
MAX(CASE WHEN {SETTLED} THEN COALESCE(settlement.finalized_at, usage.finalized_at) END) AS settled_last_finalized_at_unix_secs,
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL AND {AGGREGATABLE} THEN GREATEST(usage.response_time_ms, 0) ELSE 0 END), 0) AS response_time_sum_ms,
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL AND {AGGREGATABLE} THEN 1 ELSE 0 END), 0) AS response_time_samples
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN usage_settlement_snapshots AS settlement ON settlement.request_id = usage.request_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
) AS aggregated
@@ -207,7 +207,7 @@ JOIN (
COALESCE(SUM(CASE WHEN {SETTLED} THEN GREATEST(COALESCE(usage.cache_read_input_tokens, 0), 0) ELSE 0 END), 0) AS settled_cache_read_tokens,
MIN(CASE WHEN {SETTLED} THEN COALESCE(settlement.finalized_at, usage.finalized_at) END) AS settled_first_finalized_at_unix_secs,
MAX(CASE WHEN {SETTLED} THEN COALESCE(settlement.finalized_at, usage.finalized_at) END) AS settled_last_finalized_at_unix_secs
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN usage_settlement_snapshots AS settlement ON settlement.request_id = usage.request_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
AND usage.user_id IS NOT NULL AND usage.user_id <> '' AND {AGGREGATABLE}
@@ -266,7 +266,7 @@ JOIN (
SELECT {select_dimensions},
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL THEN GREATEST(usage.response_time_ms, 0) ELSE 0 END), 0) AS response_time_sum_ms,
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL THEN 1 ELSE 0 END), 0) AS response_time_samples
FROM `usage` AS usage
FROM `usage` AS `usage`
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ? AND {AGGREGATABLE}
GROUP BY {group_by}
) AS aggregated ON {join}
@@ -391,7 +391,7 @@ JOIN (
COALESCE(SUM(CASE WHEN {SETTLED} THEN GREATEST(COALESCE(usage.cache_read_input_tokens, 0), 0) ELSE 0 END), 0) AS settled_cache_read_tokens,
MIN(CASE WHEN {SETTLED} THEN COALESCE(settlement.finalized_at, usage.finalized_at) END) AS settled_first_finalized_at_unix_secs,
MAX(CASE WHEN {SETTLED} THEN COALESCE(settlement.finalized_at, usage.finalized_at) END) AS settled_last_finalized_at_unix_secs
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN usage_settlement_snapshots AS settlement ON settlement.request_id = usage.request_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
) AS aggregated
@@ -461,7 +461,7 @@ JOIN (
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL THEN 1 ELSE 0 END), 0) AS response_time_samples,
COALESCE(SUM({CACHE_5M}), 0) AS cache_creation_ephemeral_5m_tokens,
COALESCE(SUM({CACHE_1H}), 0) AS cache_creation_ephemeral_1h_tokens
FROM `usage` AS usage
FROM `usage` AS `usage`
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
AND {AGGREGATABLE} AND usage.model IS NOT NULL AND usage.model <> ''
GROUP BY usage.model
@@ -503,7 +503,7 @@ JOIN (
COALESCE(SUM(CASE WHEN {SETTLED} THEN GREATEST(COALESCE(usage.cache_read_input_tokens, 0), 0) ELSE 0 END), 0) AS settled_cache_read_tokens,
MIN(CASE WHEN {SETTLED} THEN COALESCE(settlement.finalized_at, usage.finalized_at) END) AS settled_first_finalized_at_unix_secs,
MAX(CASE WHEN {SETTLED} THEN COALESCE(settlement.finalized_at, usage.finalized_at) END) AS settled_last_finalized_at_unix_secs
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN usage_settlement_snapshots AS settlement ON settlement.request_id = usage.request_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
AND usage.user_id IS NOT NULL AND usage.user_id <> '' AND {AGGREGATABLE}
@@ -581,7 +581,7 @@ SELECT SHA2(UUID(), 256), usage.user_id,
COALESCE(SUM(CASE WHEN ({SUCCESS}) = 1 AND usage.response_time_ms IS NOT NULL THEN GREATEST(usage.response_time_ms, 0) ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN ({SUCCESS}) = 1 AND usage.response_time_ms IS NOT NULL THEN 1 ELSE 0 END), 0),
?, ?
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN users ON users.id = usage.user_id
LEFT JOIN usage_settlement_snapshots AS settlement ON settlement.request_id = usage.request_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
@@ -637,7 +637,7 @@ SELECT SHA2(UUID(), 256), ?, usage.model, usage.provider_name, COUNT(*),
COALESCE(SUM(COALESCE(settlement.billing_total_cost_usd, usage.total_cost_usd, 0)), 0),
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL THEN GREATEST(usage.response_time_ms, 0) ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL THEN 1 ELSE 0 END), 0), ?, ?
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN usage_settlement_snapshots AS settlement ON settlement.request_id = usage.request_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
AND usage.model IS NOT NULL AND usage.model <> '' AND {AGGREGATABLE}
@@ -669,7 +669,7 @@ SELECT SHA2(UUID(), 256), usage.user_id, MAX(COALESCE(usage.username, users.user
COALESCE(SUM(COALESCE(settlement.billing_total_cost_usd, usage.total_cost_usd, 0)), 0),
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL THEN GREATEST(usage.response_time_ms, 0) ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN usage.response_time_ms IS NOT NULL THEN 1 ELSE 0 END), 0), ?, ?
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN users ON users.id = usage.user_id
LEFT JOIN usage_settlement_snapshots AS settlement ON settlement.request_id = usage.request_id
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ?
@@ -834,7 +834,7 @@ SELECT SHA2(UUID(), 256), {user_select}?, {dimension_select_sql}
COALESCE(settlement.input_price_per_1m, usage.input_price_per_1m, 0)
* GREATEST(COALESCE(usage.cache_read_input_tokens, 0), 0) / 1000000.0
), 0), ?, ?
FROM `usage` AS usage
FROM `usage` AS `usage`
LEFT JOIN usage_settlement_snapshots AS settlement ON settlement.request_id = usage.request_id
{user_join}
WHERE usage.created_at_unix_ms >= ? AND usage.created_at_unix_ms < ? {user_filter}
@@ -7,7 +7,7 @@ use tracing::info;
// Generated by build.rs from schema/bootstrap/postgres.
pub(crate) static EMPTY_DATABASE_SNAPSHOT_SQL: &str =
include_str!(concat!(env!("OUT_DIR"), "/empty_database_snapshot.sql"));
pub(crate) const EMPTY_DATABASE_SNAPSHOT_CUTOFF_VERSION: i64 = 20260720000000;
pub(crate) const EMPTY_DATABASE_SNAPSHOT_CUTOFF_VERSION: i64 = 20260727000000;
const PUBLIC_BASE_TABLE_COUNT_SQL: &str = r#"
SELECT COUNT(*)::BIGINT
@@ -536,6 +536,7 @@ const REQUEST_BODY_DETAIL_TABLES: &[&str] = &["usage_body_blobs"];
#[cfg(all(feature = "postgres", feature = "sqlite"))]
const LIFECYCLE_TABLES: &[&str] = &["_sqlx_migrations", "schema_backfills"];
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
fn import_column_stores_timestamp(column_name: &str) -> bool {
column_name.ends_with("_at")
|| column_name.ends_with("_unix_secs")
@@ -547,6 +548,7 @@ fn import_column_stores_timestamp(column_name: &str) -> bool {
)
}
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
fn import_timestamp_uses_millis(table_name: &str, column_name: &str) -> bool {
if !column_name.ends_with("_unix_ms") {
return false;
@@ -562,6 +564,7 @@ fn import_timestamp_uses_millis(table_name: &str, column_name: &str) -> bool {
!(relation_name == "usage" && column_name == "created_at_unix_ms")
}
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
fn normalize_imported_integer_timestamp(
driver_name: &str,
table_name: &str,
@@ -597,6 +600,7 @@ fn normalize_imported_integer_timestamp(
Ok(Some(timestamp))
}
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
fn parse_imported_datetime(value: &str) -> Option<chrono::DateTime<chrono::Utc>> {
let value = value.trim();
if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(value) {
@@ -476,9 +476,9 @@ async fn load_mysql_import_columns(
let rows = sqlx::query(
r#"
SELECT
COLUMN_NAME AS column_name,
DATA_TYPE AS data_type,
COLUMN_KEY AS column_key,
CAST(COLUMN_NAME AS CHAR) AS column_name,
CAST(DATA_TYPE AS CHAR) AS data_type,
CAST(COLUMN_KEY AS CHAR) AS column_key,
ORDINAL_POSITION AS ordinal_position
FROM information_schema.columns
WHERE table_schema = DATABASE()
@@ -506,7 +506,7 @@ WHERE table_schema = DATABASE()
.eq_ignore_ascii_case("PRI")
{
primary_key.insert(
row.try_get::<i64, _>("ordinal_position").map_sql_err()?,
row.try_get::<u32, _>("ordinal_position").map_sql_err()?,
name,
);
}
@@ -662,7 +662,9 @@ fn mysql_value_to_json(row: &sqlx::mysql::MySqlRow, index: usize) -> Result<Valu
})
}
"DECIMAL" | "NEWDECIMAL" => Ok(Value::String(
row.try_get::<String, _>(index).map_sql_err()?,
row.try_get::<sqlx::types::BigDecimal, _>(index)
.map_sql_err()?
.to_string(),
)),
"VARCHAR" | "VAR_STRING" | "STRING" | "TEXT" | "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT"
| "JSON" | "ENUM" | "SET" | "DATE" | "DATETIME" | "TIMESTAMP" | "TIME" => Ok(
@@ -488,6 +488,10 @@ fn normalize_postgres_import_value(
if is_postgres_boolean_column(target_column) {
return normalize_postgres_boolean_value(column_name, value);
}
if is_postgres_integer_column(target_column) && import_column_stores_timestamp(column_name) {
return normalize_imported_integer_timestamp("postgres", table_name, column_name, value)
.map(|timestamp| timestamp.map_or(Value::Null, |value| Value::Number(value.into())));
}
if is_postgres_timestamp_column(target_column) {
return normalize_postgres_timestamp_value(table_name, column_name, value);
}
@@ -512,6 +516,13 @@ pub(super) fn is_postgres_timestamp_column(target_column: &PostgresImportColumn)
) || matches!(target_column.udt_name.as_str(), "timestamptz" | "timestamp")
}
fn is_postgres_integer_column(target_column: &PostgresImportColumn) -> bool {
matches!(
target_column.data_type.as_str(),
"smallint" | "integer" | "bigint"
) || matches!(target_column.udt_name.as_str(), "int2" | "int4" | "int8")
}
fn is_postgres_json_column(target_column: &PostgresImportColumn) -> bool {
matches!(target_column.data_type.as_str(), "json" | "jsonb")
|| matches!(target_column.udt_name.as_str(), "json" | "jsonb")
@@ -655,20 +666,6 @@ fn postgres_billing_table_name(
}
}
#[cfg(test)]
mod billing_table_tests {
use super::postgres_billing_table_name;
#[test]
fn settlement_snapshot_import_uses_request_id_conflict_key() {
assert_eq!(
postgres_billing_table_name("usage_settlement_snapshots")
.expect("settlement snapshot table should be supported"),
("public.usage_settlement_snapshots", "request_id")
);
}
}
async fn import_postgres_wallet_row(
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
row: &ExportRow,
@@ -721,3 +718,17 @@ fn postgres_wallet_table_name(
))
})
}
#[cfg(test)]
mod billing_table_tests {
use super::postgres_billing_table_name;
#[test]
fn settlement_snapshot_import_uses_request_id_conflict_key() {
assert_eq!(
postgres_billing_table_name("usage_settlement_snapshots")
.expect("settlement snapshot table should be supported"),
("public.usage_settlement_snapshots", "request_id")
);
}
}
@@ -290,6 +290,23 @@ fn cross_driver_timestamp_normalization_preserves_usage_second_contract() {
normalized["created_at_unix_ms"],
json!("2023-11-14T22:13:20+00:00")
);
let target_columns = BTreeMap::from([(
"created_at_unix_ms".to_string(),
postgres_column("bigint", "int8"),
)]);
let row = ExportRow {
id: "usage-1".to_string(),
payload: json!({ "created_at_unix_ms": "1970-01-01T00:00:01.234900Z" }),
};
let normalized = normalize_postgres_import_payload(
"public.usage",
ExportDomain::Usage,
&row,
&target_columns,
)
.expect("postgres integer usage timestamp should normalize");
assert_eq!(normalized["created_at_unix_ms"], json!(1));
}
#[test]
@@ -409,6 +409,7 @@ fn empty_database_snapshot_covers_current_cutoff_versions() {
20260718000000,
20260718010000,
20260720000000,
20260727000000,
]
);
}
@@ -1023,6 +1024,7 @@ fn mysql_and_sqlite_migrations_include_enabled_incrementals() {
20260725010000,
20260725020000,
20260725030000,
20260727000000,
]
);
assert_eq!(
@@ -1055,6 +1057,7 @@ fn mysql_and_sqlite_migrations_include_enabled_incrementals() {
20260725020000,
20260725030000,
20260725040000,
20260727000000,
]
);
}
@@ -664,7 +664,7 @@ mod tests {
let provider_id = format!(
"vertex-{}-{}",
auth_type,
api_format.replace(':', "-").replace('_', "-")
api_format.replace([':', '_'], "-")
);
let mut row = sample_row(&provider_id, api_format, "vertex-model", 10);
row.provider_type = "vertex_ai".to_string();