Skip to content

Commit 6250ad5

Browse files
Merge branch 'main' into alert-promql
2 parents 39d85b4 + 6dae911 commit 6250ad5

15 files changed

Lines changed: 125 additions & 19 deletions

File tree

src/enterprise/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ pub async fn fetch_parquet_file_paths(
9999

100100
let obs = PARSEABLE
101101
.metastore
102-
.get_all_stream_jsons(stream, None, tenant_id)
102+
.get_all_stream_jsons(stream, None, tenant_id, false)
103103
.await;
104104
if let Ok(obs) = obs {
105105
for ob in obs {

src/event/format/json.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,12 @@ impl EventFormat for Event {
109109

110110
// If there are conflicts, rename the fields in JSON values
111111
let value_arr = if !conflicts.is_empty() {
112-
rename_conflicting_fields_in_json(value_arr, &conflicts)
112+
rename_conflicting_fields_in_json(
113+
value_arr,
114+
&conflicts,
115+
stream_schema,
116+
schema_version,
117+
)
113118
} else {
114119
value_arr
115120
};

src/event/format/mod.rs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -528,9 +528,21 @@ pub fn detect_schema_conflicts(
528528

529529
/// Renames fields in JSON values according to the provided field mapping.
530530
/// Used to resolve schema conflicts by renaming fields with conflicting types.
531+
/// Renames conflicting JSON fields to typed sibling columns, but only for
532+
/// records whose value is incompatible with the existing field type.
533+
///
534+
/// `field_mapping` is produced by `detect_schema_conflicts` and maps an
535+
/// original field name to its typed sibling name, e.g. `level -> level_utf8`.
536+
/// The mapping applies per value, not blindly to every record in the batch:
537+
/// if existing schema has `level: Float64`, then `{ "level": 30 }` stays as
538+
/// `level`, while `{ "level": "info" }` becomes `{ "level_utf8": "info" }`.
539+
/// This avoids routing compatible values into the sibling column and then
540+
/// failing validation, e.g. `level_utf8` expecting `Utf8` but receiving `30`.
531541
pub fn rename_conflicting_fields_in_json(
532542
values: Vec<Value>,
533543
field_mapping: &HashMap<String, String>,
544+
existing_schema: &HashMap<String, Arc<Field>>,
545+
schema_version: SchemaVersion,
534546
) -> Vec<Value> {
535547
if field_mapping.is_empty() {
536548
return values;
@@ -544,7 +556,13 @@ pub fn rename_conflicting_fields_in_json(
544556
.into_iter()
545557
.map(|(key, val)| {
546558
if let Some(new_key) = field_mapping.get(&key) {
547-
(new_key.clone(), val)
559+
if existing_schema.get(&key).is_some_and(|field| {
560+
value_compatible_with_type(&val, field.data_type(), schema_version)
561+
}) {
562+
(key, val)
563+
} else {
564+
(new_key.clone(), val)
565+
}
548566
} else {
549567
(key, val)
550568
}
@@ -776,8 +794,8 @@ mod tests {
776794
#[test]
777795
fn test_rename_conflicting_fields_in_json() {
778796
let values = vec![
779-
json!({"body_timestamp": "2025-01-01T00:00:00Z", "message": "hello"}),
780-
json!({"body_timestamp": "2025-01-02T00:00:00Z", "message": "world"}),
797+
json!({"body_timestamp": "not a timestamp", "message": "hello"}),
798+
json!({"body_timestamp": "also not a timestamp", "message": "world"}),
781799
];
782800

783801
let mut field_mapping = HashMap::new();
@@ -786,7 +804,22 @@ mod tests {
786804
"body_timestamp_timestamp_ms".to_string(),
787805
);
788806

789-
let renamed = rename_conflicting_fields_in_json(values, &field_mapping);
807+
let mut existing_schema: HashMap<String, Arc<Field>> = HashMap::new();
808+
existing_schema.insert(
809+
"body_timestamp".to_string(),
810+
Arc::new(Field::new(
811+
"body_timestamp",
812+
DataType::Timestamp(TimeUnit::Millisecond, None),
813+
true,
814+
)),
815+
);
816+
817+
let renamed = rename_conflicting_fields_in_json(
818+
values,
819+
&field_mapping,
820+
&existing_schema,
821+
SchemaVersion::V1,
822+
);
790823

791824
assert_eq!(renamed.len(), 2);
792825
assert!(renamed[0].get("body_timestamp_timestamp_ms").is_some());
@@ -799,12 +832,44 @@ mod tests {
799832
let values = vec![json!({"body_timestamp": "2025-01-01T00:00:00Z"})];
800833

801834
let field_mapping = HashMap::new();
802-
let renamed = rename_conflicting_fields_in_json(values.clone(), &field_mapping);
835+
let existing_schema = HashMap::new();
836+
let renamed = rename_conflicting_fields_in_json(
837+
values.clone(),
838+
&field_mapping,
839+
&existing_schema,
840+
SchemaVersion::V1,
841+
);
803842

804843
// Should return values unchanged
805844
assert_eq!(renamed, values);
806845
}
807846

847+
#[test]
848+
fn test_rename_conflicting_fields_in_json_only_incompatible_records() {
849+
let values = vec![json!({"level": 30}), json!({"level": "info"})];
850+
851+
let mut field_mapping = HashMap::new();
852+
field_mapping.insert("level".to_string(), "level_utf8".to_string());
853+
854+
let mut existing_schema: HashMap<String, Arc<Field>> = HashMap::new();
855+
existing_schema.insert(
856+
"level".to_string(),
857+
Arc::new(Field::new("level", DataType::Float64, true)),
858+
);
859+
860+
let renamed = rename_conflicting_fields_in_json(
861+
values,
862+
&field_mapping,
863+
&existing_schema,
864+
SchemaVersion::V1,
865+
);
866+
867+
assert_eq!(renamed[0].get("level"), Some(&json!(30)));
868+
assert!(renamed[0].get("level_utf8").is_none());
869+
assert_eq!(renamed[1].get("level_utf8"), Some(&json!("info")));
870+
assert!(renamed[1].get("level").is_none());
871+
}
872+
808873
#[test]
809874
fn test_detect_schema_conflicts_timestamp_vs_utf8() {
810875
// Existing schema has body_timestamp as Timestamp

src/handlers/http/cluster/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ pub async fn fetch_stats_from_ingestors(
872872
) -> Result<Vec<utils::QueriedStats>, StreamError> {
873873
let obs = PARSEABLE
874874
.metastore
875-
.get_all_stream_jsons(stream_name, Some(Mode::Ingest), tenant_id)
875+
.get_all_stream_jsons(stream_name, Some(Mode::Ingest), tenant_id, false)
876876
.await?;
877877

878878
let mut ingestion_size = 0u64;

src/handlers/http/middleware.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ where
186186
permissions,
187187
&user.tenant,
188188
);
189+
req.headers_mut().insert(
190+
HeaderName::from_static(TENANT_ID),
191+
HeaderValue::from_str(tenant).unwrap(),
192+
);
189193
req.extensions_mut().insert(session_key);
190194
Some(session_id)
191195
}

src/handlers/http/modal/query/querier_logstream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ pub async fn get_stats(
197197
if !date_value.is_empty() {
198198
let obs = PARSEABLE
199199
.metastore
200-
.get_all_stream_jsons(&stream_name, None, &tenant_id)
200+
.get_all_stream_jsons(&stream_name, None, &tenant_id, false)
201201
.await?;
202202

203203
let mut stream_jsons = Vec::new();

src/handlers/http/oidc.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use url::Url;
4444
use crate::{
4545
handlers::{
4646
COOKIE_AGE_DAYS, SESSION_COOKIE_NAME, USER_COOKIE_NAME, USER_ID_COOKIE_NAME,
47-
http::modal::OIDC_CLIENT,
47+
http::{cluster::sync_user_creation, modal::OIDC_CLIENT},
4848
},
4949
oauth::OAuthSession,
5050
parseable::{DEFAULT_TENANT, PARSEABLE},
@@ -238,6 +238,21 @@ pub async fn reply_login(
238238
}
239239
};
240240

241+
if !PARSEABLE.options.is_multi_tenant() {
242+
let roles = Some(user.roles.clone());
243+
if let Err(e) = sync_user_creation(
244+
&req,
245+
user.clone(),
246+
&roles,
247+
&tenant_id,
248+
&PARSEABLE.options.username,
249+
)
250+
.await
251+
{
252+
tracing::error!("Failed to sync OAuth user with roles to cluster nodes: {e}");
253+
}
254+
}
255+
241256
let id = Ulid::new();
242257
Users.new_session(&user, SessionKey::SessionId(id), expires_in);
243258

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub mod handlers;
3232
pub mod hottier;
3333
pub mod interactive;
3434
mod livetail;
35-
mod metadata;
35+
pub mod metadata;
3636
pub mod metastore;
3737
pub mod metrics;
3838
pub mod migration;

src/metastore/metastore_traits.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ pub trait Metastore: std::fmt::Debug + Send + Sync {
247247
stream_name: &str,
248248
mode: Option<Mode>,
249249
tenant_id: &Option<String>,
250+
is_migration: bool,
250251
) -> Result<Vec<Bytes>, MetastoreError>;
251252

252253
/// Fetch manifests only for the explicitly requested date keys

src/metastore/metastores/object_store_metastore.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,7 @@ impl Metastore for ObjectStoreMetastore {
892892
stream_name: &str,
893893
mode: Option<Mode>,
894894
tenant_id: &Option<String>,
895+
_is_migration: bool,
895896
) -> Result<Vec<Bytes>, MetastoreError> {
896897
let root = tenant_id.as_deref().unwrap_or("");
897898
let path = RelativePathBuf::from_iter([root, stream_name, STREAM_ROOT_DIRECTORY]);

0 commit comments

Comments
 (0)