Skip to main content

rpfm_server/state/
tables.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! Table operations: merge, update, TSV import/export, key deletes, and lookups of table data
12//! and references across the open packs and the dependencies.
13
14use anyhow::{anyhow, Result};
15use rayon::prelude::*;
16
17use serde_json::{Number, Value};
18
19use std::collections::BTreeMap;
20
21use std::borrow::Cow;
22use std::collections::{HashMap, HashSet};
23use std::path::Path;
24
25use rpfm_extensions::dependencies::{Dependencies, KEY_DELETES_TABLE_NAME, TableReferences};
26use rpfm_extensions::merge::{db_baseline, delta_merge_db, delta_merge_loc, loc_baseline, MergeConflict, MergeOptions, MergeResolution};
27
28use rpfm_ipc::api::ApiError;
29use rpfm_ipc::api::files::{ASSEMBLY_KIT_TABLE_FILE_NAME, FileRef, FileSource};
30use rpfm_ipc::api::session::{DependencyTableData, DependencyTables};
31use rpfm_ipc::api::references::{DEFAULT_REFERENCE_VALUES_LIMIT, DEFAULT_USAGES_LIMIT, FindUsages, FoundRow, GetReferenceValues, RowLoc, RowLocs, GetTableReferenceData, TableReferenceData, LocSource, ReferenceValue, ReferenceValues, RowLocation, Usage, Usages};
32use rpfm_ipc::api::tables::{AddKeyDeletes, ExportTsv, FilesEdited, ImportTsv, MergeTables, RenameKey, TableUpgraded, TablesMerged, UpgradeTable};
33use rpfm_ipc::api::tables::{ColumnValues, DEFAULT_VALUES_LIMIT, GetColumnValues, GetUnusedNumbers, MAX_UNUSED_NUMBERS, UnusedNumbers, GetTableDefinition, DEFAULT_ROWS_LIMIT, EditTable, FilterOp, GetTableRows, RowEdit, RowFilter, RowRef, TableEdited, TableInfo, TableRow, TableRows};
34use rpfm_ipc::helpers::{DataSource, RFileInfo};
35
36use rpfm_lib::files::{Container, ContainerPath, db::DB, DecodeableExtraData, FileType, pack::Pack, RFile, RFileDecoded, table::{DecodedData, local::TableInMemory, Table}};
37use rpfm_lib::schema::{Definition, Field, FieldType};
38use rpfm_lib::utils::current_time;
39
40use super::{SessionState, loaded_schema, pack, pack_mut};
41
42/// A [`RowEdit`] with its values converted to the types of their columns, and its indexes checked.
43enum PreparedEdit {
44    Insert(usize, Vec<DecodedData>),
45    Update(usize, Vec<(usize, DecodedData)>),
46
47    /// Indexes to delete, sorted from last to first so removing one doesn't move the rest.
48    Delete(Vec<usize>),
49}
50
51/// A [`RowFilter`] ready to check rows: its column index resolved, and its value lowercased if it ignores case.
52struct PreparedFilter {
53    column_index: usize,
54    op: FilterOp,
55    value: String,
56    ignore_case: bool,
57}
58
59/// Result of merging tables.
60#[derive(Debug)]
61pub enum MergeOutcome {
62
63    /// The tables were merged into a new file at this path.
64    Merged(String),
65
66    /// Rows that couldn't be reconciled automatically. Nothing was written.
67    Conflicts(Vec<MergeConflict>),
68}
69
70/// Result of a delta merge attempt: either a finished file ready to insert, or the conflicts blocking it.
71enum DeltaMergeOutcome {
72    Merged(RFile),
73    Conflicts(Vec<MergeConflict>),
74}
75
76impl SessionState {
77
78    /// Merges compatible tables of a pack into a new one.
79    ///
80    /// # Arguments
81    ///
82    /// * `pack_key` - Key of the pack with the tables.
83    /// * `paths` - Paths of the tables to merge.
84    /// * `merged_path` - Path of the merged table.
85    /// * `delete_source_files` - If the merged tables are deleted afterwards.
86    /// * `options` - How to merge the rows.
87    ///
88    /// # Returns
89    ///
90    /// The path of the merged table, or the conflicts that prevented the merge.
91    pub fn merge_files(&mut self, pack_key: &str, paths: &[ContainerPath], merged_path: &str, delete_source_files: bool, options: &MergeOptions) -> Result<MergeOutcome> {
92        let pack = pack_mut(&mut self.packs, pack_key)?;
93        let files_to_merge = pack.files_by_paths(paths, false);
94
95        let merge_result = if *options.delta_merge() {
96            delta_merge_files(&files_to_merge, merged_path, &self.dependencies, options.resolutions())?
97        } else {
98            DeltaMergeOutcome::Merged(RFile::merge(&files_to_merge, merged_path)?)
99        };
100
101        match merge_result {
102            DeltaMergeOutcome::Merged(file) => {
103                let _ = pack.insert(file);
104
105                // Make sure to only delete the files if they're not the destination file.
106                if delete_source_files {
107                    paths.iter()
108                        .filter(|path| merged_path != path.path_raw())
109                        .for_each(|path| { pack.remove(path); });
110                }
111
112                Ok(MergeOutcome::Merged(merged_path.to_owned()))
113            },
114            DeltaMergeOutcome::Conflicts(conflicts) => Ok(MergeOutcome::Conflicts(conflicts)),
115        }
116    }
117
118    /// Updates a table of a pack to the version of the table in the game files.
119    ///
120    /// # Returns
121    ///
122    /// The old and new version, and the names of the fields deleted and added.
123    pub fn update_table(&mut self, pack_key: &str, path: &ContainerPath) -> Result<(i32, i32, Vec<String>, Vec<String>)> {
124        let path = path.path_raw();
125        let file = pack_mut(&mut self.packs, pack_key)?.file_mut(path, false)
126            .ok_or_else(|| anyhow!("File not found in the open Pack: {}", path))?;
127
128        let decoded = file.decoded_mut().map_err(|_| anyhow!("File with the following path undecoded: {}", path))?;
129        Ok(self.dependencies.update_db(decoded)?)
130    }
131
132    /// Exports a table to a TSV file.
133    ///
134    /// # Arguments
135    ///
136    /// * `pack_key` - Key of the pack with the table. Only used if `data_source` is a pack.
137    /// * `internal_path` - Path of the table in its source.
138    /// * `external_path` - Path of the TSV file to write.
139    /// * `data_source` - Where the table is.
140    /// * `tsv_keys_first` - If the TSV uses the old column order, with keys first.
141    pub fn export_tsv(&mut self, pack_key: &str, internal_path: &str, external_path: &Path, data_source: DataSource, tsv_keys_first: bool) -> Result<()> {
142        let schema = loaded_schema(&self.schema)?;
143        let file = match data_source {
144            DataSource::PackFile => self.packs.get_mut(pack_key).and_then(|pack| pack.file_mut(internal_path, false)),
145            DataSource::ParentFiles => self.dependencies.file_mut(internal_path, false, true).ok(),
146            DataSource::GameFiles => self.dependencies.file_mut(internal_path, true, false).ok(),
147            DataSource::AssKitFiles => return Err(anyhow!("Exporting a TSV from the Assembly Kit is not yet supported.")),
148            DataSource::ExternalFile => return Err(anyhow!("Exporting a TSV from a external file is not yet supported.")),
149        }.ok_or_else(|| anyhow!("File with the following path not found in the Pack: {}", internal_path))?;
150
151        file.tsv_export_to_path(external_path, schema, tsv_keys_first)?;
152        Ok(())
153    }
154
155    /// Replaces a table of a pack with the contents of a TSV file, keeping the table's GUID.
156    ///
157    /// # Returns
158    ///
159    /// The imported table.
160    pub fn import_tsv(&mut self, pack_key: &str, internal_path: &str, external_path: &Path) -> Result<RFileDecoded> {
161        let file = pack_mut(&mut self.packs, pack_key)?.file_mut(internal_path, false)
162            .ok_or_else(|| anyhow!("File with the following path not found in the Pack: {}", internal_path))?;
163
164        // Preserve the original table GUID, as set_decoded would replace it with a fresh
165        // one from the imported table, making the import non-idempotent for DB tables.
166        let original_guid = match file.decoded() {
167            Ok(RFileDecoded::DB(table)) => Some(table.guid().to_owned()),
168            _ => None,
169        };
170
171        let imported = RFile::tsv_import_from_path(external_path, &self.schema)?;
172        let mut decoded = imported.decoded()?.clone();
173        if let (RFileDecoded::DB(table), Some(guid)) = (&mut decoded, original_guid) {
174            table.set_guid(guid);
175        }
176
177        file.set_decoded(decoded.clone())?;
178        Ok(decoded)
179    }
180
181    /// Changes values referencing an edited key in every table of a pack.
182    ///
183    /// # Arguments
184    ///
185    /// * `pack_key` - Key of the pack to edit.
186    /// * `table_name` - Name of the table whose key was edited.
187    /// * `definition` - Definition of the table whose key was edited.
188    /// * `changes` - Edited field, and its value before and after the edit, of each change.
189    ///
190    /// # Returns
191    ///
192    /// The paths of the edited files, and their info.
193    pub fn cascade_edition(&mut self, pack_key: &str, table_name: &str, definition: &Definition, changes: &[(Field, String, String)]) -> Result<(Vec<ContainerPath>, Vec<RFileInfo>)> {
194        let pack = pack_mut(&mut self.packs, pack_key)?;
195        let edited_paths = match self.schema {
196            Some(ref schema) => changes.iter()
197                .flat_map(|(field, value_before, value_after)| DB::cascade_edition(pack, schema, table_name, field, definition, value_before, value_after))
198                .collect::<Vec<_>>(),
199            None => vec![],
200        };
201
202        let files_info = pack.files_by_paths(&edited_paths, false).into_par_iter().map(From::from).collect();
203        Ok((edited_paths, files_info))
204    }
205
206    /// Adds rows to a key deletes table of a pack, one per key.
207    ///
208    /// # Arguments
209    ///
210    /// * `pack_key` - Key of the pack with the key deletes table.
211    /// * `table_file_name` - File name of the key deletes table.
212    /// * `key_table_name` - Name of the table the keys belong to.
213    /// * `keys` - Keys to delete.
214    ///
215    /// # Returns
216    ///
217    /// The path of the edited table, or `None` if the table wasn't found.
218    pub fn add_keys_to_key_deletes(&mut self, pack_key: &str, table_file_name: &str, key_table_name: &str, keys: &HashSet<String>) -> Result<Option<ContainerPath>> {
219        let path = ContainerPath::File(format!("db/{KEY_DELETES_TABLE_NAME}/{table_file_name}"));
220        let mut files = pack_mut(&mut self.packs, pack_key)?.files_by_type_and_paths_mut(&[FileType::DB], &[path], true);
221
222        let Some(file) = files.first_mut() else { return Ok(None) };
223        let Ok(RFileDecoded::DB(db)) = file.decoded_mut() else { return Ok(None) };
224
225        for key in keys {
226            db.data_mut().push(vec![
227                DecodedData::StringU8(key.to_owned()),
228                DecodedData::StringU8(key_table_name.to_owned()),
229            ]);
230        }
231
232        Ok(Some(file.path_in_container()))
233    }
234
235    /// Returns the valid values of the reference columns of a table, from the open packs and the dependencies.
236    ///
237    /// # Arguments
238    ///
239    /// * `table_name` - Name of the table.
240    /// * `definition` - Definition of the table.
241    /// * `force_local_generation` - If the references to the open packs are regenerated even if they're cached.
242    ///
243    /// # Returns
244    ///
245    /// The references of each reference column, by column index. Empty if there is no schema.
246    pub fn reference_data(&mut self, table_name: &str, definition: &Definition, force_local_generation: bool) -> HashMap<i32, TableReferences> {
247        let Some(ref schema) = self.schema else { return HashMap::new() };
248
249        if force_local_generation || !self.dependencies.local_tables_references().contains_key(table_name) {
250            self.dependencies.generate_local_definition_references(schema, table_name, definition);
251        }
252
253        self.dependencies.db_reference_data(schema, &self.packs, table_name, definition, &None)
254    }
255
256    /// Returns the values each column of a table can reference, with their lookups.
257    ///
258    /// # Errors
259    ///
260    /// Fails if there is no schema, or neither it nor the Assembly Kit tables have a definition of the table with the requested version.
261    pub fn table_reference_data(&mut self, request: &GetTableReferenceData) -> Result<TableReferenceData> {
262        let definition = self.definition_by_name_and_version(&request.table_name, request.version)?.clone();
263
264        Ok(TableReferenceData { columns: self.reference_data(&request.table_name, &definition, request.regenerate) })
265    }
266
267    /// Finds the first row with a value in a column of a table.
268    ///
269    /// Searches the open packs, then the parent packs, the game files and the Assembly Kit tables.
270    ///
271    /// # Arguments
272    ///
273    /// * `pack_key` - If set, only this open pack is searched.
274    /// * `table_name` - Name of the table, like `factions_tables`.
275    /// * `column` - Name of the column. If it's localised, the first key column is searched instead.
276    /// * `value` - Value to search.
277    ///
278    /// # Returns
279    ///
280    /// Where the row is, and its values.
281    pub fn find_definition(&self, pack_key: Option<&str>, table_name: &str, column: &str, value: &str) -> Result<FoundRow> {
282        if loaded_schema(&self.schema)?.definitions_by_table_name(table_name).is_none_or(|definitions| definitions.is_empty()) {
283            return Err(ApiError::DefinitionNotFound(table_name.to_owned()).into());
284        }
285
286        let table_folders = ContainerPath::db_table_folders(table_name);
287        for (key, pack) in self.packs_to_search(pack_key)? {
288            if let Some(found) = find_in_db_files(&pack.files_by_paths(&table_folders, true), column, value, &FileSource::Pack(key.to_owned())) {
289                return Ok(found);
290            }
291        }
292
293        if pack_key.is_none() {
294            for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
295                if let Ok(files) = self.dependencies.db_data(table_name, include_vanilla, include_parent) {
296                    if let Some(found) = find_in_db_files(&files, column, value, &source) {
297                        return Ok(found);
298                    }
299                }
300            }
301
302            if let Some(table) = self.dependencies.asskit_only_db_tables().get(table_name) {
303                if let Some((column_index, row_index)) = find_in_db(table, column, value) {
304                    let path = format!("db/{table_name}/{ASSEMBLY_KIT_TABLE_FILE_NAME}");
305                    let location = RowLocation { source: FileSource::AssemblyKit, path, column_index, row_index };
306                    return Ok(FoundRow { location, values: row_values_by_column(table.table(), row_index) });
307                }
308            }
309        }
310
311        Err(ApiError::NotFound(format!("The value {value} of the column {column} of {table_name}")).into())
312    }
313
314    /// Finds the first row of a Loc file with a key.
315    ///
316    /// Searches the open packs, then the parent packs and then the game files.
317    ///
318    /// # Arguments
319    ///
320    /// * `pack_key` - If set, only this open pack is searched.
321    /// * `loc_key` - Key to search.
322    ///
323    /// # Returns
324    ///
325    /// Where the row is, and its values.
326    pub fn find_loc(&self, pack_key: Option<&str>, loc_key: &str) -> Result<FoundRow> {
327        for (key, pack) in self.packs_to_search(pack_key)? {
328            if let Some(found) = find_in_loc_files(&pack.files_by_type(&[FileType::Loc]), loc_key, &FileSource::Pack(key.to_owned())) {
329                return Ok(found);
330            }
331        }
332
333        if pack_key.is_none() {
334            for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
335                if let Ok(files) = self.dependencies.loc_data(include_vanilla, include_parent) {
336                    if let Some(found) = find_in_loc_files(&files, loc_key, &source) {
337                        return Ok(found);
338                    }
339                }
340            }
341        }
342
343        Err(ApiError::NotFound(format!("The loc key {loc_key}")).into())
344    }
345
346    /// Returns the loc entries of a row of a DB table: the loc key of each localised column, with its text if it has one.
347    ///
348    /// # Errors
349    ///
350    /// Fails if the file is not a DB table, if the row doesn't exist, or if the schema doesn't say which columns make the loc keys of the table.
351    pub fn row_locs(&mut self, row_ref: &RowRef) -> Result<RowLocs> {
352        let table_name = row_ref.file.path.strip_prefix("db/")
353            .and_then(|path| path.split('/').next())
354            .ok_or_else(|| ApiError::NotATable(row_ref.file.path.clone()))?
355            .to_owned();
356
357        let table = self.table(&row_ref.file)?;
358        let definition = table.definition();
359        let data = table.data();
360        let row = data.get(row_ref.index)
361            .ok_or_else(|| ApiError::InvalidParams(format!("Row {} of {} doesn't exist.", row_ref.index, row_ref.file.path)))?;
362
363        if definition.localised_fields().is_empty() {
364            return Ok(RowLocs::default());
365        }
366
367        if definition.localised_key_order().is_empty() {
368            return Err(ApiError::InvalidParams(format!("The schema doesn't say which columns make the loc keys of {table_name}.")).into());
369        }
370
371        let key = definition.localised_key_order().iter().map(|index| row[*index as usize].data_to_string()).collect::<String>();
372        let table_name = table_name.strip_suffix("_tables").unwrap_or(&table_name);
373        let keys = definition.localised_fields().iter()
374            .map(|field| (field.name().to_owned(), format!("{table_name}_{}_{key}", field.name())))
375            .collect::<Vec<_>>();
376
377        let locs = keys.into_iter()
378            .map(|(column, key)| {
379                let text = self.find_loc(None, &key).ok()
380                    .and_then(|found| found.values.get("text").and_then(Value::as_str).map(str::to_owned));
381                RowLoc { column, key, text }
382            })
383            .collect();
384
385        Ok(RowLocs { locs })
386    }
387
388    /// Returns the open packs a lookup searches, with their keys: only the provided one if set, or all of them.
389    ///
390    /// # Errors
391    ///
392    /// Fails if the provided pack is not open.
393    // The keys come either from `pack_key` or from the open packs, so both share the lifetime of the result.
394    fn packs_to_search<'a>(&'a self, pack_key: Option<&'a str>) -> Result<Vec<(&'a str, &'a Pack)>> {
395        Ok(match pack_key {
396            Some(pack_key) => vec![(pack_key, pack(&self.packs, pack_key)?)],
397            None => self.packs.iter().map(|(key, pack)| (key.as_str(), pack)).collect(),
398        })
399    }
400
401    /// Finds every row referencing a value, in the open packs, the parent packs and the game files.
402    ///
403    /// # Arguments
404    ///
405    /// * `pack_key` - If set, only this open pack is searched.
406    /// * `reference_map` - Columns to search, by table name.
407    /// * `value` - Value to search.
408    ///
409    /// # Returns
410    ///
411    /// The rows referencing the value.
412    pub fn search_references(&self, pack_key: Option<&str>, reference_map: &HashMap<String, Vec<String>>, value: &str) -> Result<Vec<Usage>> {
413        let paths = reference_map.keys().flat_map(|table_name| ContainerPath::db_table_folders(table_name)).collect::<Vec<ContainerPath>>();
414        let packs = self.packs_to_search(pack_key)?;
415
416        let mut references = vec![];
417        for (key, pack) in packs {
418            let files = pack.files_by_paths(&paths, true);
419            let source = FileSource::Pack(key.to_owned());
420            for (table_name, columns) in reference_map {
421                for file in &files {
422                    if file.db_table_name_from_path() == Some(table_name.as_str()) {
423                        references.extend(references_in_file(file, columns, value, &source));
424                    }
425                }
426            }
427        }
428
429        if pack_key.is_some() {
430            return Ok(references);
431        }
432
433        for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
434            for (table_name, columns) in reference_map {
435                if let Ok(tables) = self.dependencies.db_data(table_name, include_vanilla, include_parent) {
436                    references.par_extend(tables.par_iter().flat_map(|table| references_in_file(table, columns, value, &source)));
437                }
438            }
439        }
440
441        Ok(references)
442    }
443
444    /// Finds the rows of other tables referencing a value of a table, taking the referencing columns from the schema.
445    pub fn find_usages(&self, request: &FindUsages) -> Result<Usages> {
446        let schema = loaded_schema(&self.schema)?;
447        let version = match request.version {
448            Some(version) => version,
449            None => self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version,
450        };
451
452        let definition = self.definition_by_name_and_version(&request.table_name, version)?;
453        let reference_map = schema.referencing_columns_for_table(&request.table_name, definition)
454            .remove(&request.column)
455            .unwrap_or_default();
456
457        let usages = self.search_references(request.pack.as_deref(), &reference_map, &request.value)?;
458        let total = usages.len();
459        let usages = usages.into_iter()
460            .skip(request.offset)
461            .take(request.limit.unwrap_or(DEFAULT_USAGES_LIMIT))
462            .collect();
463
464        Ok(Usages { usages, total })
465    }
466
467    /// Returns the table, column and key values a loc key was generated from, if it can be found in the dependencies.
468    pub fn loc_key_source(&self, loc_key: &str) -> Option<(String, String, Vec<String>)> {
469        self.dependencies.loc_key_source(loc_key)
470    }
471
472    /// Returns the distinct values of a column of a table.
473    ///
474    /// # Arguments
475    ///
476    /// * `table_name` - Name of the table, like `factions_tables`.
477    /// * `column_name` - Name of the column.
478    /// * `include_packs` - If the open packs are searched.
479    /// * `include_dependencies` - If the game files and parent packs are searched.
480    pub fn column_values(&self, table_name: &str, column_name: &str, include_packs: bool, include_dependencies: bool) -> HashSet<String> {
481        let packs = if include_packs { Some(&self.packs) } else { None };
482        self.dependencies.db_values_from_table_name_and_column_name(packs, table_name, column_name, include_dependencies, include_dependencies)
483    }
484
485    /// Returns a page of the distinct values of a column of a table, sorted.
486    ///
487    /// # Errors
488    ///
489    /// Fails if the schema has no definition of the table, or if the table has no column with the provided name.
490    pub fn column_values_page(&self, request: &GetColumnValues) -> Result<ColumnValues> {
491        let definition = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?;
492        if !definition.columns.iter().any(|column| column.name == request.column) {
493            return Err(ApiError::InvalidParams(format!("The table has no column named {}.", request.column)).into());
494        }
495
496        let mut values = self.column_values(&request.table_name, &request.column, request.include_packs, request.include_dependencies)
497            .into_iter()
498            .filter(|value| value.starts_with(&request.prefix))
499            .collect::<Vec<_>>();
500        values.sort_unstable();
501
502        let total = values.len();
503        let values = values.into_iter()
504            .skip(request.offset)
505            .take(request.limit.unwrap_or(DEFAULT_VALUES_LIMIT))
506            .collect();
507
508        Ok(ColumnValues { values, total })
509    }
510
511    /// Returns numbers above the highest one used in an integer column of a table, in the open packs and the dependencies.
512    ///
513    /// # Errors
514    ///
515    /// Fails if the schema has no definition of the table, if the table has no column with the provided name,
516    /// if the column isn't an integer one, or if its type has not enough numbers left.
517    pub fn unused_numbers(&self, request: &GetUnusedNumbers) -> Result<UnusedNumbers> {
518        if request.count > MAX_UNUSED_NUMBERS {
519            return Err(ApiError::InvalidParams(format!("Up to {MAX_UNUSED_NUMBERS} numbers can be requested at once.")).into());
520        }
521
522        let definition = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?;
523        let column = definition.columns.iter()
524            .find(|column| column.name == request.column)
525            .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
526
527        let highest_allowed = match column.field_type {
528            FieldType::I16 | FieldType::OptionalI16 => i64::from(i16::MAX),
529            FieldType::I32 | FieldType::OptionalI32 => i64::from(i32::MAX),
530            FieldType::I64 | FieldType::OptionalI64 => i64::MAX,
531            FieldType::Boolean |
532            FieldType::F32 |
533            FieldType::F64 |
534            FieldType::ColourRGB |
535            FieldType::StringU8 |
536            FieldType::StringU16 |
537            FieldType::OptionalStringU8 |
538            FieldType::OptionalStringU16 |
539            FieldType::SequenceU16(_) |
540            FieldType::SequenceU32(_) => return Err(ApiError::InvalidParams(format!("The column {} is not an integer column.", request.column)).into()),
541        };
542
543        let highest_used = self.column_values(&request.table_name, &request.column, true, true).iter()
544            .filter_map(|value| value.parse::<i64>().ok())
545            .max()
546            .unwrap_or(0);
547
548        let numbers = (1..=request.count as i64)
549            .map(|offset| highest_used.checked_add(offset).filter(|number| *number <= highest_allowed))
550            .collect::<Option<Vec<_>>>()
551            .ok_or_else(|| ApiError::InvalidParams(format!("The column {} has less than {} numbers left above {highest_used}.", request.column, request.count)))?;
552
553        Ok(UnusedNumbers { numbers })
554    }
555
556    /// Returns a page of the values a reference column of a table can have, with their display text.
557    ///
558    /// # Errors
559    ///
560    /// Fails if the table has no column with the provided name, or if the column is not a reference.
561    pub fn reference_values(&mut self, request: &GetReferenceValues) -> Result<ReferenceValues> {
562        let version = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version;
563        let definition = loaded_schema(&self.schema)?.definition_by_name_and_version(&request.table_name, version)
564            .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?
565            .clone();
566
567        let column_index = definition.fields_processed().iter()
568            .position(|field| field.name() == request.column)
569            .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
570
571        let references = self.reference_data(&request.table_name, &definition, false).remove(&(column_index as i32))
572            .ok_or_else(|| ApiError::InvalidParams(format!("The column {} is not a reference column.", request.column)))?;
573
574        let mut values = references.data().iter()
575            .filter(|(value, _)| value.starts_with(&request.prefix))
576            .map(|(value, lookup)| ReferenceValue { value: value.clone(), lookup: lookup.clone() })
577            .collect::<Vec<_>>();
578        values.sort_unstable_by(|a, b| a.value.cmp(&b.value));
579
580        let total = values.len();
581        let values = values.into_iter()
582            .skip(request.offset)
583            .take(request.limit.unwrap_or(DEFAULT_REFERENCE_VALUES_LIMIT))
584            .collect();
585
586        Ok(ReferenceValues { values, total })
587    }
588
589    /// Returns the table, column and key values a loc key was generated from, if it can be found in the dependencies.
590    pub fn loc_source(&self, loc_key: &str) -> Option<LocSource> {
591        self.loc_key_source(loc_key).map(|(table, column, key_values)| LocSource { table_name: format!("{table}_tables"), column, key_values })
592    }
593
594    /// Returns the names of the tables in the game files.
595    pub fn dependency_table_names(&self) -> Vec<String> {
596        self.dependencies.vanilla_loose_tables().keys()
597            .chain(self.dependencies.vanilla_tables().keys())
598            .cloned()
599            .collect()
600    }
601
602    /// Returns the version of a table in the game files.
603    ///
604    /// Startpos, twad and CEO tables not in the game files return the latest version in the schema.
605    pub fn dependency_table_version(&self, table_name: &str) -> Result<i32> {
606        if !self.dependencies.is_vanilla_data_loaded(false) {
607            return Err(ApiError::DependenciesNotLoaded.into());
608        }
609
610        if let Some(version) = self.dependencies.db_version(table_name) {
611            return Ok(version);
612        }
613
614        if !(table_name.starts_with("start_pos_") || table_name.starts_with("twad_") || table_name.starts_with("ceo")) {
615            return Err(anyhow!("Table not found in the game files."));
616        }
617
618        loaded_schema(&self.schema)?.definitions_by_table_name(table_name)
619            .and_then(|definitions| definitions.first())
620            .map(|definition| *definition.version())
621            .ok_or_else(|| anyhow!("There are no definitions for this specific table."))
622    }
623
624    /// Returns the tables with the provided name from the game files and the parent packs.
625    pub fn dependency_tables(&self, table_name: &str) -> Result<Vec<RFile>> {
626        Ok(self.dependencies.db_data(table_name, true, true)?.into_iter().cloned().collect())
627    }
628
629    /// Returns the tables of the game files, and the custom tables of the schema, with their version.
630    pub fn dependency_table_versions(&self) -> DependencyTables {
631        let names = self.dependency_table_names().into_iter().chain(self.custom_table_names().unwrap_or_default());
632        let tables = names.filter_map(|name| self.dependency_table_version(&name).ok().map(|version| (name, version))).collect();
633        DependencyTables { tables }
634    }
635
636    /// Returns every decoded table of a type in the game files and the parent packs.
637    pub fn dependency_table_data(&self, table_name: &str) -> Result<DependencyTableData> {
638        let tables = self.dependency_tables(table_name)?.iter()
639            .filter_map(|file| match file.decoded() {
640                Ok(RFileDecoded::DB(table)) => Some(table.clone()),
641                _ => None,
642            })
643            .collect();
644
645        Ok(DependencyTableData { tables })
646    }
647}
648
649impl SessionState {
650
651    /// Returns the definition and row count of a table.
652    pub fn table_info(&mut self, file: &FileRef) -> Result<TableInfo> {
653        let table = self.table(file)?;
654        Ok(TableInfo {
655            table_name: table.name().to_owned(),
656            version: *table.definition().version(),
657            columns: table.definition().fields_processed().to_vec(),
658            row_count: table.len(),
659        })
660    }
661
662    /// Returns a page of the rows of a table matching the request's filters, with only the requested columns.
663    ///
664    /// # Errors
665    ///
666    /// Fails if the table can't be read, or if the request names a column the table doesn't have.
667    pub fn table_rows(&mut self, request: &GetTableRows) -> Result<TableRows> {
668        let table = self.table(&request.file)?;
669        let fields = table.definition().fields_processed();
670        let column_index = |name: &str| fields.iter()
671            .position(|field| field.name() == name)
672            .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {name}.")));
673
674        let columns = match request.columns {
675            Some(ref names) => names.iter()
676                .map(|name| column_index(name).map(|index| (index, name.to_owned())))
677                .collect::<Result<Vec<_>, _>>()?,
678            None => fields.iter().enumerate().map(|(index, field)| (index, field.name().to_owned())).collect(),
679        };
680
681        let filters = request.filters.iter()
682            .map(|filter| column_index(&filter.column).map(|column_index| PreparedFilter::new(filter, column_index)))
683            .collect::<Result<Vec<_>, _>>()?;
684
685        let limit = request.limit.unwrap_or(DEFAULT_ROWS_LIMIT);
686        let mut rows = vec![];
687        let mut total = 0;
688
689        for (index, row) in table.data().iter().enumerate() {
690            if !filters.iter().all(|filter| filter.matches(row)) {
691                continue;
692            }
693
694            if total >= request.offset && rows.len() < limit {
695                let values = columns.iter()
696                    .map(|(column_index, _)| row.get(*column_index).map(decoded_to_json).unwrap_or(Value::Null))
697                    .collect();
698
699                rows.push(TableRow { index, values });
700            }
701
702            total += 1;
703        }
704
705        Ok(TableRows {
706            columns: columns.into_iter().map(|(_, name)| name).collect(),
707            rows,
708            total,
709        })
710    }
711
712    /// Edits rows of a table of an open pack. If any edit fails, none is applied.
713    ///
714    /// # Errors
715    ///
716    /// Fails if the pack doesn't have the table, or if an edit names a column the table doesn't have,
717    /// a row that doesn't exist, or a value that doesn't fit its column.
718    pub fn edit_table(&mut self, request: &EditTable) -> Result<TableEdited> {
719        let file = FileRef { source: FileSource::Pack(request.pack.clone()), path: request.path.clone() };
720        let fields = self.table(&file)?.definition().fields_processed().to_vec();
721        let copied = self.copied_row_values(&request.edits, &fields)?;
722
723        let row_count = match self.decoded_table_file(&file)?.decoded_mut()? {
724            RFileDecoded::DB(table) => {
725                let edits = prepare_row_edits(&request.edits, &fields, table.new_row(), table.data().len(), &copied)?;
726                apply_row_edits(table.data_mut(), edits)
727            }
728            RFileDecoded::Loc(table) => {
729                let edits = prepare_row_edits(&request.edits, &fields, table.new_row(), table.data().len(), &copied)?;
730                apply_row_edits(table.data_mut(), edits)
731            }
732            _ => return Err(ApiError::NotATable(request.path.clone()).into()),
733        };
734
735        Ok(TableEdited { row_count })
736    }
737
738    /// Returns the values of the rows the inserts of some edits copy, by edit index, in the columns of the edited table with the same names.
739    ///
740    /// # Arguments
741    ///
742    /// * `edits` - The edits, some of which may copy rows.
743    /// * `fields` - Columns of the edited table, as rows see them.
744    ///
745    /// # Errors
746    ///
747    /// Fails if a copied row doesn't exist, or if one of its values can't be converted to the type of its column in the edited table.
748    fn copied_row_values(&mut self, edits: &[RowEdit], fields: &[Field]) -> Result<BTreeMap<usize, Vec<(usize, DecodedData)>>> {
749        let mut copied = BTreeMap::new();
750        for (edit_index, edit) in edits.iter().enumerate() {
751            let RowEdit::Insert { copy_from: Some(row_ref), .. } = edit else {
752                continue;
753            };
754
755            let invalid = |message: String| ApiError::InvalidParams(format!("Edit {edit_index}: {message}"));
756            let table = self.table(&row_ref.file)?;
757            let source_fields = table.definition().fields_processed();
758            let data = table.data();
759            let row = data.get(row_ref.index)
760                .ok_or_else(|| invalid(format!("row {} of {} doesn't exist.", row_ref.index, row_ref.file.path)))?;
761
762            let values = fields.iter().enumerate()
763                .filter_map(|(column_index, field)| {
764                    let source_index = source_fields.iter().position(|source_field| source_field.name() == field.name())?;
765                    Some(copied_value(field.field_type(), &row[source_index])
766                        .map(|value| (column_index, value))
767                        .map_err(|_| invalid(format!("the value of the column {} can't be copied, as its type is different.", field.name()))))
768                })
769                .collect::<Result<Vec<_>, _>>()?;
770
771            copied.insert(edit_index, values);
772        }
773
774        Ok(copied)
775    }
776
777    /// Merges tables of the same type of a pack into a new one.
778    ///
779    /// # Returns
780    ///
781    /// The path of the merged table, or the conflicts that prevented the merge.
782    pub fn merge_tables(&mut self, request: &MergeTables) -> Result<TablesMerged> {
783        let paths = self.pack_container_paths(&request.pack, &request.paths)?;
784        let mut options = MergeOptions::default();
785        options.set_delta_merge(request.delta);
786        options.set_resolutions(request.resolutions.clone());
787
788        Ok(match self.merge_files(&request.pack, &paths, &request.merged_path, request.delete_sources, &options)? {
789            MergeOutcome::Merged(path) => TablesMerged { merged: Some(path), conflicts: vec![] },
790            MergeOutcome::Conflicts(conflicts) => TablesMerged { merged: None, conflicts },
791        })
792    }
793
794    /// Updates a table of a pack to the version it has in the game files.
795    pub fn upgrade_table(&mut self, request: &UpgradeTable) -> Result<TableUpgraded> {
796        let (old_version, new_version, deleted_columns, added_columns) = self.update_table(&request.pack, &ContainerPath::File(request.path.clone()))?;
797        Ok(TableUpgraded { old_version, new_version, deleted_columns, added_columns })
798    }
799
800    /// Changes a value of a key column in every table of a pack, including the columns referencing it.
801    ///
802    /// # Errors
803    ///
804    /// Fails if the table has no definition, or no column with the provided name.
805    pub fn rename_key(&mut self, request: &RenameKey) -> Result<FilesEdited> {
806        let version = match request.version {
807            Some(version) => version,
808            None => self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version,
809        };
810
811        let definition = self.definition_by_name_and_version(&request.table_name, version)?.clone();
812        let field = definition.fields_processed().iter()
813            .find(|field| field.name() == request.column)
814            .cloned()
815            .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
816
817        let changes = [(field, request.old_value.clone(), request.new_value.clone())];
818        let (edited, _) = self.cascade_edition(&request.pack, &request.table_name, &definition, &changes)?;
819        Ok(FilesEdited { edited: edited.iter().map(|path| path.path_raw().to_owned()).collect() })
820    }
821
822    /// Adds rows to a key deletes table of a pack, one per key.
823    ///
824    /// # Errors
825    ///
826    /// Fails if the key deletes table doesn't exist in the pack.
827    pub fn add_key_deletes(&mut self, request: &AddKeyDeletes) -> Result<FilesEdited> {
828        let keys = request.keys.iter().cloned().collect::<HashSet<_>>();
829        let path = self.add_keys_to_key_deletes(&request.pack, &request.file_name, &request.table_name, &keys)?
830            .ok_or_else(|| ApiError::FileNotFound(format!("db/{KEY_DELETES_TABLE_NAME}/{}", request.file_name)))?;
831
832        Ok(FilesEdited { edited: vec![path.path_raw().to_owned()] })
833    }
834
835    /// Writes a table from any source, except the Assembly Kit, to a TSV file.
836    ///
837    /// # Arguments
838    ///
839    /// * `request` - Table to export, and where.
840    /// * `keys_first` - If the TSV uses the old column order, with keys first.
841    pub fn export_table_tsv(&mut self, request: &ExportTsv, keys_first: bool) -> Result<()> {
842        let (pack_key, data_source) = match request.file.source {
843            FileSource::Pack(ref pack_key) => (pack_key.as_str(), DataSource::PackFile),
844            FileSource::GameFiles => ("", DataSource::GameFiles),
845            FileSource::ParentFiles => ("", DataSource::ParentFiles),
846            FileSource::AssemblyKit => return Err(ApiError::InvalidParams("Assembly Kit tables can't be exported to TSV.".to_owned()).into()),
847        };
848
849        self.export_tsv(pack_key, &request.file.path, &request.destination, data_source, keys_first)
850    }
851
852    /// Replaces a table of a pack with the contents of a TSV file, keeping its GUID.
853    pub fn import_table_tsv(&mut self, request: &ImportTsv) -> Result<TableEdited> {
854        let row_count = match self.import_tsv(&request.pack, &request.path, &request.tsv_path)? {
855            RFileDecoded::DB(table) => table.len(),
856            RFileDecoded::Loc(table) => table.len(),
857            _ => return Err(ApiError::NotATable(request.path.clone()).into()),
858        };
859
860        Ok(TableEdited { row_count })
861    }
862
863    /// Returns a DB or Loc table from any source, decoding it first if needed.
864    fn table(&mut self, file: &FileRef) -> Result<&TableInMemory> {
865        self.check_file_exists(file)?;
866
867        if file.source == FileSource::AssemblyKit {
868            let not_found = || ApiError::FileNotFound(file.path.clone());
869            let table_name = file.path.split('/').nth(1).ok_or_else(not_found)?;
870            let table = self.dependencies.asskit_only_db_tables().get(table_name).ok_or_else(not_found)?;
871            return Ok(table.table());
872        }
873
874        match self.decoded_table_file(file)?.decoded()? {
875            RFileDecoded::DB(table) => Ok(table.table()),
876            RFileDecoded::Loc(table) => Ok(table.table()),
877            _ => Err(ApiError::NotATable(file.path.clone()).into()),
878        }
879    }
880
881    /// Returns a DB or Loc file from an open pack or the dependencies, decoded.
882    fn decoded_table_file(&mut self, file: &FileRef) -> Result<&mut RFile> {
883        let rfile = match file.source {
884            FileSource::Pack(ref pack_key) => pack_mut(&mut self.packs, pack_key)?.files_mut().get_mut(&file.path),
885            FileSource::GameFiles => self.dependencies.file_mut(&file.path, true, false).ok(),
886            FileSource::ParentFiles => self.dependencies.file_mut(&file.path, false, true).ok(),
887
888            // Assembly Kit tables are kept decoded, outside of any file.
889            FileSource::AssemblyKit => return Err(ApiError::ReadOnly(file.path.clone()).into()),
890        }.ok_or_else(|| ApiError::FileNotFound(file.path.clone()))?;
891
892        let file_type = rfile.file_type();
893        if file_type != FileType::DB && file_type != FileType::Loc {
894            return Err(ApiError::NotATable(file.path.clone()).into());
895        }
896
897        if file_type == FileType::DB && self.schema.is_none() {
898            return Err(ApiError::SchemaNotLoaded.into());
899        }
900
901        let mut extra_data = DecodeableExtraData::default();
902        extra_data.set_schema(self.schema.as_ref());
903        rfile.decode(&Some(extra_data), true, false)?;
904        Ok(rfile)
905    }
906}
907
908impl PreparedFilter {
909
910    /// Prepares a filter to check rows, with the index of its column.
911    fn new(filter: &RowFilter, column_index: usize) -> Self {
912        Self {
913            column_index,
914            op: filter.op,
915            value: if filter.ignore_case { filter.value.to_lowercase() } else { filter.value.clone() },
916            ignore_case: filter.ignore_case,
917        }
918    }
919
920    /// Returns if the value of the filter's column in a row matches the filter.
921    fn matches(&self, row: &[DecodedData]) -> bool {
922        let Some(data) = row.get(self.column_index) else { return false };
923        let value = data.data_to_string();
924        let value = if self.ignore_case { Cow::Owned(value.to_lowercase()) } else { value };
925
926        match self.op {
927            FilterOp::Equals => *value == self.value,
928            FilterOp::NotEquals => *value != self.value,
929            FilterOp::Contains => value.contains(&self.value),
930            FilterOp::StartsWith => value.starts_with(&self.value),
931            FilterOp::EndsWith => value.ends_with(&self.value),
932        }
933    }
934}
935
936/// Converts row edits to the types of their columns, and checks their indexes against the rows the table will have when each one is applied.
937///
938/// # Arguments
939///
940/// * `edits` - The edits to prepare.
941/// * `fields` - Columns of the table, as rows see them.
942/// * `new_row` - A row with the default value of each column.
943/// * `row_count` - Amount of rows of the table before the edits.
944/// * `copied` - Values of the rows the inserts copy, by edit index and column index.
945fn prepare_row_edits(edits: &[RowEdit], fields: &[Field], new_row: Vec<DecodedData>, mut row_count: usize, copied: &BTreeMap<usize, Vec<(usize, DecodedData)>>) -> Result<Vec<PreparedEdit>, ApiError> {
946    let mut prepared = Vec::with_capacity(edits.len());
947    for (edit_index, edit) in edits.iter().enumerate() {
948        let invalid = |message: String| ApiError::InvalidParams(format!("Edit {edit_index}: {message}"));
949        let missing_row = |index: usize, row_count: usize| invalid(format!("row {index} doesn't exist, the table has {row_count} rows at this point."));
950
951        match edit {
952            RowEdit::Insert { index, values, .. } => {
953                let index = index.unwrap_or(row_count);
954                if index > row_count {
955                    return Err(missing_row(index, row_count));
956                }
957
958                let mut row = new_row.clone();
959                let copied_values = copied.get(&edit_index).into_iter().flatten().cloned();
960                for (column_index, value) in copied_values.chain(row_values(fields, values).map_err(invalid)?) {
961                    row[column_index] = value;
962                }
963
964                prepared.push(PreparedEdit::Insert(index, row));
965                row_count += 1;
966            }
967            RowEdit::Update { index, values } => {
968                if *index >= row_count {
969                    return Err(missing_row(*index, row_count));
970                }
971
972                prepared.push(PreparedEdit::Update(*index, row_values(fields, values).map_err(invalid)?));
973            }
974            RowEdit::Delete { indexes } => {
975                let mut indexes = indexes.clone();
976                indexes.sort_unstable_by(|a, b| b.cmp(a));
977                indexes.dedup();
978
979                if let Some(index) = indexes.first().filter(|index| **index >= row_count) {
980                    return Err(missing_row(*index, row_count));
981                }
982
983                row_count -= indexes.len();
984                prepared.push(PreparedEdit::Delete(indexes));
985            }
986        }
987    }
988
989    Ok(prepared)
990}
991
992/// Applies prepared row edits to the rows of a table.
993///
994/// # Returns
995///
996/// The amount of rows after the edits.
997fn apply_row_edits(rows: &mut Vec<Vec<DecodedData>>, edits: Vec<PreparedEdit>) -> usize {
998    for edit in edits {
999        match edit {
1000            PreparedEdit::Insert(index, row) => rows.insert(index, row),
1001            PreparedEdit::Update(index, values) => {
1002                for (column_index, value) in values {
1003                    rows[index][column_index] = value;
1004                }
1005            }
1006            PreparedEdit::Delete(indexes) => {
1007                for index in indexes {
1008                    rows.remove(index);
1009                }
1010            }
1011        }
1012    }
1013
1014    rows.len()
1015}
1016
1017/// Converts values given by column name to the types of their columns.
1018///
1019/// # Returns
1020///
1021/// The index of the column of each value, and the converted value.
1022fn row_values(fields: &[Field], values: &BTreeMap<String, Value>) -> Result<Vec<(usize, DecodedData)>, String> {
1023    values.iter()
1024        .map(|(column, value)| {
1025            let column_index = fields.iter()
1026                .position(|field| field.name() == column)
1027                .ok_or_else(|| format!("the table has no column named {column}."))?;
1028
1029            let value = json_to_decoded(fields[column_index].field_type(), value)
1030                .map_err(|error| format!("invalid value for column {column}: {error}"))?;
1031
1032            Ok((column_index, value))
1033        })
1034        .collect()
1035}
1036
1037/// Returns a copy of a table value for a column of the provided type, converting it if it's of another type.
1038fn copied_value(field_type: &FieldType, value: &DecodedData) -> Result<DecodedData> {
1039    if value.is_field_type_correct(field_type) {
1040        Ok(value.clone())
1041    } else {
1042        Ok(DecodedData::new_from_type_and_string(field_type, &value.data_to_string())?)
1043    }
1044}
1045
1046/// Converts a JSON value to a table value of the provided type.
1047///
1048/// Booleans, numbers and strings are accepted, as long as their text parses as the type.
1049fn json_to_decoded(field_type: &FieldType, value: &Value) -> Result<DecodedData> {
1050    let text = match value {
1051        Value::String(text) => Cow::Borrowed(text.as_str()),
1052        Value::Bool(_) | Value::Number(_) => Cow::Owned(value.to_string()),
1053        Value::Null | Value::Array(_) | Value::Object(_) => return Err(anyhow!("expected a boolean, number or string, found {value}.")),
1054    };
1055
1056    DecodedData::new_from_type_and_string(field_type, &text)
1057        .map_err(|_| anyhow!("expected {}, found {value}.", expected_value(field_type)))
1058}
1059
1060/// Returns how the values a column of the provided type accepts are described in errors.
1061fn expected_value(field_type: &FieldType) -> &'static str {
1062    match field_type {
1063        FieldType::Boolean => "a boolean",
1064        FieldType::F32 | FieldType::F64 => "a number",
1065        FieldType::I16 | FieldType::OptionalI16 => "a 16-bit integer",
1066        FieldType::I32 | FieldType::OptionalI32 => "a 32-bit integer",
1067        FieldType::I64 | FieldType::OptionalI64 => "a 64-bit integer",
1068        FieldType::ColourRGB => "a hexadecimal colour, like FF0000",
1069        FieldType::StringU8 |
1070        FieldType::StringU16 |
1071        FieldType::OptionalStringU8 |
1072        FieldType::OptionalStringU16 |
1073        FieldType::SequenceU16(_) |
1074        FieldType::SequenceU32(_) => "a string",
1075    }
1076}
1077
1078/// Returns a table value as JSON: booleans and numbers as themselves, everything else as text.
1079///
1080/// Floats not representable in JSON (NaN, infinite) become `null`.
1081fn decoded_to_json(data: &DecodedData) -> Value {
1082    match data {
1083        DecodedData::Boolean(value) => Value::Bool(*value),
1084
1085        // Parsing the f32's shortest text form keeps values like 0.1 exact, instead of 0.10000000149011612.
1086        DecodedData::F32(value) => value.to_string().parse::<f64>().ok().and_then(Number::from_f64).map_or(Value::Null, Value::Number),
1087        DecodedData::F64(value) => Number::from_f64(*value).map_or(Value::Null, Value::Number),
1088        DecodedData::I16(value) | DecodedData::OptionalI16(value) => Value::from(*value),
1089        DecodedData::I32(value) | DecodedData::OptionalI32(value) => Value::from(*value),
1090        DecodedData::I64(value) | DecodedData::OptionalI64(value) => Value::from(*value),
1091        DecodedData::ColourRGB(value) |
1092        DecodedData::StringU8(value) |
1093        DecodedData::StringU16(value) |
1094        DecodedData::OptionalStringU8(value) |
1095        DecodedData::OptionalStringU16(value) => Value::String(value.clone()),
1096        DecodedData::SequenceU16(_) |
1097        DecodedData::SequenceU32(_) => Value::String(data.data_to_string().into_owned()),
1098    }
1099}
1100
1101/// Finds the first row with a value in a column of a table.
1102///
1103/// If the column is localised, it's not in the table's data, so the first key column is searched instead.
1104///
1105/// # Returns
1106///
1107/// The column and row indexes of the row.
1108fn find_in_db(table: &DB, column_name: &str, value: &str) -> Option<(usize, usize)> {
1109    let definition = table.definition();
1110    let key_column_name = if definition.localised_fields().iter().any(|field| field.name() == column_name) {
1111        definition.localised_key_order().first()
1112            .and_then(|index| definition.fields_processed().get(*index as usize).map(|field| field.name().to_owned()))
1113    } else {
1114        None
1115    };
1116
1117    let column_name = key_column_name.as_deref().unwrap_or(column_name);
1118    let (column_index, row_indexes) = table.table().rows_containing_data(column_name, value)?;
1119    row_indexes.first().map(|row_index| (column_index, *row_index))
1120}
1121
1122/// Finds the first row with a value in a column, among DB files.
1123fn find_in_db_files(files: &[&RFile], column_name: &str, value: &str, source: &FileSource) -> Option<FoundRow> {
1124    files.iter().find_map(|file| match file.decoded() {
1125        Ok(RFileDecoded::DB(table)) => find_in_db(table, column_name, value)
1126            .map(|(column_index, row_index)| FoundRow {
1127                location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index },
1128                values: row_values_by_column(table.table(), row_index),
1129            }),
1130        _ => None,
1131    })
1132}
1133
1134/// Finds the first row with a key, among Loc files.
1135fn find_in_loc_files(files: &[&RFile], loc_key: &str, source: &FileSource) -> Option<FoundRow> {
1136    files.iter().find_map(|file| match file.decoded() {
1137        Ok(RFileDecoded::Loc(table)) => {
1138            let (column_index, row_indexes) = table.table().rows_containing_data("key", loc_key)?;
1139            row_indexes.first().map(|row_index| FoundRow {
1140                location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index: *row_index },
1141                values: row_values_by_column(table.table(), *row_index),
1142            })
1143        }
1144        _ => None,
1145    })
1146}
1147
1148/// Returns the values of a row of a table, by column name.
1149fn row_values_by_column(table: &TableInMemory, row_index: usize) -> BTreeMap<String, Value> {
1150    let data = table.data();
1151    let Some(row) = data.get(row_index) else {
1152        return BTreeMap::new();
1153    };
1154
1155    table.definition().fields_processed().iter()
1156        .zip(row)
1157        .map(|(field, value)| (field.name().to_owned(), decoded_to_json(value)))
1158        .collect()
1159}
1160
1161/// Returns every row of a DB file with a value in any of the provided columns.
1162fn references_in_file(file: &RFile, columns: &[String], value: &str, source: &FileSource) -> Vec<Usage> {
1163    let Ok(RFileDecoded::DB(table)) = file.decoded() else { return vec![] };
1164
1165    columns.iter()
1166        .filter_map(|column_name| table.table().rows_containing_data(column_name, value).map(|found| (column_name, found)))
1167        .flat_map(|(column_name, (column_index, row_indexes))| row_indexes.into_iter()
1168            .map(|row_index| Usage {
1169                location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index },
1170                column: column_name.to_owned(),
1171                values: row_values_by_column(table.table(), row_index),
1172            })
1173            .collect::<Vec<_>>())
1174        .collect()
1175}
1176
1177/// Delta-merges the decoded DB or Loc `sources` (same type/table) against the vanilla/parent baseline,
1178/// applying any already-known `resolutions`. See [`rpfm_extensions::merge`] for the merge rules.
1179fn delta_merge_files(sources: &[&RFile], merged_path: &str, dependencies: &Dependencies, resolutions: &[MergeResolution]) -> Result<DeltaMergeOutcome> {
1180    if sources.len() < 2 {
1181        return Err(anyhow!("Not enough tables provided to merge."));
1182    }
1183
1184    let (merged, conflicts) = match sources[0].decoded()? {
1185        RFileDecoded::DB(_) => {
1186            let tables = sources.iter()
1187                .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some((file.path_in_container_raw(), table)) } else { None })
1188                .collect::<Vec<_>>();
1189
1190            let baseline = db_baseline(dependencies, tables[0].1.table_name());
1191            let (merged, conflicts) = delta_merge_db(&tables, baseline.as_ref(), resolutions)?;
1192            (RFileDecoded::DB(merged), conflicts)
1193        },
1194        RFileDecoded::Loc(_) => {
1195            let tables = sources.iter()
1196                .filter_map(|file| if let Ok(RFileDecoded::Loc(table)) = file.decoded() { Some((file.path_in_container_raw(), table)) } else { None })
1197                .collect::<Vec<_>>();
1198
1199            let baseline = loc_baseline(dependencies);
1200            let (merged, conflicts) = delta_merge_loc(&tables, baseline.as_ref(), resolutions)?;
1201            (RFileDecoded::Loc(merged), conflicts)
1202        },
1203        _ => return Err(anyhow!("Delta merge is only supported for DB and Loc tables.")),
1204    };
1205
1206    if conflicts.is_empty() {
1207        Ok(DeltaMergeOutcome::Merged(RFile::new_from_decoded(&merged, current_time()?, merged_path)))
1208    } else {
1209        Ok(DeltaMergeOutcome::Conflicts(conflicts))
1210    }
1211}
1212
1213//-------------------------------------------------------------------------------//
1214//                                   Tests
1215//-------------------------------------------------------------------------------//
1216
1217#[cfg(test)]
1218#[path = "tables_test.rs"]
1219mod tables_test;