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 it has no definition of the table with the requested version.
261    pub fn table_reference_data(&mut self, request: &GetTableReferenceData) -> Result<TableReferenceData> {
262        let definition = loaded_schema(&self.schema)?.definition_by_name_and_version(&request.table_name, request.version)
263            .cloned()
264            .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?;
265
266        Ok(TableReferenceData { columns: self.reference_data(&request.table_name, &definition, request.regenerate) })
267    }
268
269    /// Finds the first row with a value in a column of a table.
270    ///
271    /// Searches the open packs, then the parent packs, the game files and the Assembly Kit tables.
272    ///
273    /// # Arguments
274    ///
275    /// * `pack_key` - If set, only this open pack is searched.
276    /// * `table_name` - Name of the table, like `factions_tables`.
277    /// * `column` - Name of the column. If it's localised, the first key column is searched instead.
278    /// * `value` - Value to search.
279    ///
280    /// # Returns
281    ///
282    /// Where the row is, and its values.
283    pub fn find_definition(&self, pack_key: Option<&str>, table_name: &str, column: &str, value: &str) -> Result<FoundRow> {
284        if loaded_schema(&self.schema)?.definitions_by_table_name(table_name).is_none_or(|definitions| definitions.is_empty()) {
285            return Err(ApiError::DefinitionNotFound(table_name.to_owned()).into());
286        }
287
288        let table_folders = ContainerPath::db_table_folders(table_name);
289        for (key, pack) in self.packs_to_search(pack_key)? {
290            if let Some(found) = find_in_db_files(&pack.files_by_paths(&table_folders, true), column, value, &FileSource::Pack(key.to_owned())) {
291                return Ok(found);
292            }
293        }
294
295        if pack_key.is_none() {
296            for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
297                if let Ok(files) = self.dependencies.db_data(table_name, include_vanilla, include_parent) {
298                    if let Some(found) = find_in_db_files(&files, column, value, &source) {
299                        return Ok(found);
300                    }
301                }
302            }
303
304            if let Some(table) = self.dependencies.asskit_only_db_tables().get(table_name) {
305                if let Some((column_index, row_index)) = find_in_db(table, column, value) {
306                    let path = format!("db/{table_name}/{ASSEMBLY_KIT_TABLE_FILE_NAME}");
307                    let location = RowLocation { source: FileSource::AssemblyKit, path, column_index, row_index };
308                    return Ok(FoundRow { location, values: row_values_by_column(table.table(), row_index) });
309                }
310            }
311        }
312
313        Err(ApiError::NotFound(format!("The value {value} of the column {column} of {table_name}")).into())
314    }
315
316    /// Finds the first row of a Loc file with a key.
317    ///
318    /// Searches the open packs, then the parent packs and then the game files.
319    ///
320    /// # Arguments
321    ///
322    /// * `pack_key` - If set, only this open pack is searched.
323    /// * `loc_key` - Key to search.
324    ///
325    /// # Returns
326    ///
327    /// Where the row is, and its values.
328    pub fn find_loc(&self, pack_key: Option<&str>, loc_key: &str) -> Result<FoundRow> {
329        for (key, pack) in self.packs_to_search(pack_key)? {
330            if let Some(found) = find_in_loc_files(&pack.files_by_type(&[FileType::Loc]), loc_key, &FileSource::Pack(key.to_owned())) {
331                return Ok(found);
332            }
333        }
334
335        if pack_key.is_none() {
336            for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
337                if let Ok(files) = self.dependencies.loc_data(include_vanilla, include_parent) {
338                    if let Some(found) = find_in_loc_files(&files, loc_key, &source) {
339                        return Ok(found);
340                    }
341                }
342            }
343        }
344
345        Err(ApiError::NotFound(format!("The loc key {loc_key}")).into())
346    }
347
348    /// 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.
349    ///
350    /// # Errors
351    ///
352    /// 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.
353    pub fn row_locs(&mut self, row_ref: &RowRef) -> Result<RowLocs> {
354        let table_name = row_ref.file.path.strip_prefix("db/")
355            .and_then(|path| path.split('/').next())
356            .ok_or_else(|| ApiError::NotATable(row_ref.file.path.clone()))?
357            .to_owned();
358
359        let table = self.table(&row_ref.file)?;
360        let definition = table.definition();
361        let data = table.data();
362        let row = data.get(row_ref.index)
363            .ok_or_else(|| ApiError::InvalidParams(format!("Row {} of {} doesn't exist.", row_ref.index, row_ref.file.path)))?;
364
365        if definition.localised_fields().is_empty() {
366            return Ok(RowLocs::default());
367        }
368
369        if definition.localised_key_order().is_empty() {
370            return Err(ApiError::InvalidParams(format!("The schema doesn't say which columns make the loc keys of {table_name}.")).into());
371        }
372
373        let key = definition.localised_key_order().iter().map(|index| row[*index as usize].data_to_string()).collect::<String>();
374        let table_name = table_name.strip_suffix("_tables").unwrap_or(&table_name);
375        let keys = definition.localised_fields().iter()
376            .map(|field| (field.name().to_owned(), format!("{table_name}_{}_{key}", field.name())))
377            .collect::<Vec<_>>();
378
379        let locs = keys.into_iter()
380            .map(|(column, key)| {
381                let text = self.find_loc(None, &key).ok()
382                    .and_then(|found| found.values.get("text").and_then(Value::as_str).map(str::to_owned));
383                RowLoc { column, key, text }
384            })
385            .collect();
386
387        Ok(RowLocs { locs })
388    }
389
390    /// Returns the open packs a lookup searches, with their keys: only the provided one if set, or all of them.
391    ///
392    /// # Errors
393    ///
394    /// Fails if the provided pack is not open.
395    // The keys come either from `pack_key` or from the open packs, so both share the lifetime of the result.
396    fn packs_to_search<'a>(&'a self, pack_key: Option<&'a str>) -> Result<Vec<(&'a str, &'a Pack)>> {
397        Ok(match pack_key {
398            Some(pack_key) => vec![(pack_key, pack(&self.packs, pack_key)?)],
399            None => self.packs.iter().map(|(key, pack)| (key.as_str(), pack)).collect(),
400        })
401    }
402
403    /// Finds every row referencing a value, in the open packs, the parent packs and the game files.
404    ///
405    /// # Arguments
406    ///
407    /// * `pack_key` - If set, only this open pack is searched.
408    /// * `reference_map` - Columns to search, by table name.
409    /// * `value` - Value to search.
410    ///
411    /// # Returns
412    ///
413    /// The rows referencing the value.
414    pub fn search_references(&self, pack_key: Option<&str>, reference_map: &HashMap<String, Vec<String>>, value: &str) -> Result<Vec<Usage>> {
415        let paths = reference_map.keys().flat_map(|table_name| ContainerPath::db_table_folders(table_name)).collect::<Vec<ContainerPath>>();
416        let packs = self.packs_to_search(pack_key)?;
417
418        let mut references = vec![];
419        for (key, pack) in packs {
420            let files = pack.files_by_paths(&paths, true);
421            let source = FileSource::Pack(key.to_owned());
422            for (table_name, columns) in reference_map {
423                for file in &files {
424                    if file.db_table_name_from_path() == Some(table_name.as_str()) {
425                        references.extend(references_in_file(file, columns, value, &source));
426                    }
427                }
428            }
429        }
430
431        if pack_key.is_some() {
432            return Ok(references);
433        }
434
435        for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
436            for (table_name, columns) in reference_map {
437                if let Ok(tables) = self.dependencies.db_data(table_name, include_vanilla, include_parent) {
438                    references.par_extend(tables.par_iter().flat_map(|table| references_in_file(table, columns, value, &source)));
439                }
440            }
441        }
442
443        Ok(references)
444    }
445
446    /// Finds the rows of other tables referencing a value of a table, taking the referencing columns from the schema.
447    pub fn find_usages(&self, request: &FindUsages) -> Result<Usages> {
448        let schema = loaded_schema(&self.schema)?;
449        let version = match request.version {
450            Some(version) => version,
451            None => self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version,
452        };
453
454        let definition = schema.definition_by_name_and_version(&request.table_name, version)
455            .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?;
456
457        let reference_map = schema.referencing_columns_for_table(&request.table_name, definition)
458            .remove(&request.column)
459            .unwrap_or_default();
460
461        let usages = self.search_references(request.pack.as_deref(), &reference_map, &request.value)?;
462        let total = usages.len();
463        let usages = usages.into_iter()
464            .skip(request.offset)
465            .take(request.limit.unwrap_or(DEFAULT_USAGES_LIMIT))
466            .collect();
467
468        Ok(Usages { usages, total })
469    }
470
471    /// Returns the table, column and key values a loc key was generated from, if it can be found in the dependencies.
472    pub fn loc_key_source(&self, loc_key: &str) -> Option<(String, String, Vec<String>)> {
473        self.dependencies.loc_key_source(loc_key)
474    }
475
476    /// Returns the distinct values of a column of a table.
477    ///
478    /// # Arguments
479    ///
480    /// * `table_name` - Name of the table, like `factions_tables`.
481    /// * `column_name` - Name of the column.
482    /// * `include_packs` - If the open packs are searched.
483    /// * `include_dependencies` - If the game files and parent packs are searched.
484    pub fn column_values(&self, table_name: &str, column_name: &str, include_packs: bool, include_dependencies: bool) -> HashSet<String> {
485        let packs = if include_packs { Some(&self.packs) } else { None };
486        self.dependencies.db_values_from_table_name_and_column_name(packs, table_name, column_name, include_dependencies, include_dependencies)
487    }
488
489    /// Returns a page of the distinct values of a column of a table, sorted.
490    ///
491    /// # Errors
492    ///
493    /// Fails if the schema has no definition of the table, or if the table has no column with the provided name.
494    pub fn column_values_page(&self, request: &GetColumnValues) -> Result<ColumnValues> {
495        let definition = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?;
496        if !definition.columns.iter().any(|column| column.name == request.column) {
497            return Err(ApiError::InvalidParams(format!("The table has no column named {}.", request.column)).into());
498        }
499
500        let mut values = self.column_values(&request.table_name, &request.column, request.include_packs, request.include_dependencies)
501            .into_iter()
502            .filter(|value| value.starts_with(&request.prefix))
503            .collect::<Vec<_>>();
504        values.sort_unstable();
505
506        let total = values.len();
507        let values = values.into_iter()
508            .skip(request.offset)
509            .take(request.limit.unwrap_or(DEFAULT_VALUES_LIMIT))
510            .collect();
511
512        Ok(ColumnValues { values, total })
513    }
514
515    /// Returns numbers above the highest one used in an integer column of a table, in the open packs and the dependencies.
516    ///
517    /// # Errors
518    ///
519    /// Fails if the schema has no definition of the table, if the table has no column with the provided name,
520    /// if the column isn't an integer one, or if its type has not enough numbers left.
521    pub fn unused_numbers(&self, request: &GetUnusedNumbers) -> Result<UnusedNumbers> {
522        if request.count > MAX_UNUSED_NUMBERS {
523            return Err(ApiError::InvalidParams(format!("Up to {MAX_UNUSED_NUMBERS} numbers can be requested at once.")).into());
524        }
525
526        let definition = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?;
527        let column = definition.columns.iter()
528            .find(|column| column.name == request.column)
529            .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
530
531        let highest_allowed = match column.field_type {
532            FieldType::I16 | FieldType::OptionalI16 => i64::from(i16::MAX),
533            FieldType::I32 | FieldType::OptionalI32 => i64::from(i32::MAX),
534            FieldType::I64 | FieldType::OptionalI64 => i64::MAX,
535            FieldType::Boolean |
536            FieldType::F32 |
537            FieldType::F64 |
538            FieldType::ColourRGB |
539            FieldType::StringU8 |
540            FieldType::StringU16 |
541            FieldType::OptionalStringU8 |
542            FieldType::OptionalStringU16 |
543            FieldType::SequenceU16(_) |
544            FieldType::SequenceU32(_) => return Err(ApiError::InvalidParams(format!("The column {} is not an integer column.", request.column)).into()),
545        };
546
547        let highest_used = self.column_values(&request.table_name, &request.column, true, true).iter()
548            .filter_map(|value| value.parse::<i64>().ok())
549            .max()
550            .unwrap_or(0);
551
552        let numbers = (1..=request.count as i64)
553            .map(|offset| highest_used.checked_add(offset).filter(|number| *number <= highest_allowed))
554            .collect::<Option<Vec<_>>>()
555            .ok_or_else(|| ApiError::InvalidParams(format!("The column {} has less than {} numbers left above {highest_used}.", request.column, request.count)))?;
556
557        Ok(UnusedNumbers { numbers })
558    }
559
560    /// Returns a page of the values a reference column of a table can have, with their display text.
561    ///
562    /// # Errors
563    ///
564    /// Fails if the table has no column with the provided name, or if the column is not a reference.
565    pub fn reference_values(&mut self, request: &GetReferenceValues) -> Result<ReferenceValues> {
566        let version = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version;
567        let definition = loaded_schema(&self.schema)?.definition_by_name_and_version(&request.table_name, version)
568            .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?
569            .clone();
570
571        let column_index = definition.fields_processed().iter()
572            .position(|field| field.name() == request.column)
573            .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
574
575        let references = self.reference_data(&request.table_name, &definition, false).remove(&(column_index as i32))
576            .ok_or_else(|| ApiError::InvalidParams(format!("The column {} is not a reference column.", request.column)))?;
577
578        let mut values = references.data().iter()
579            .filter(|(value, _)| value.starts_with(&request.prefix))
580            .map(|(value, lookup)| ReferenceValue { value: value.clone(), lookup: lookup.clone() })
581            .collect::<Vec<_>>();
582        values.sort_unstable_by(|a, b| a.value.cmp(&b.value));
583
584        let total = values.len();
585        let values = values.into_iter()
586            .skip(request.offset)
587            .take(request.limit.unwrap_or(DEFAULT_REFERENCE_VALUES_LIMIT))
588            .collect();
589
590        Ok(ReferenceValues { values, total })
591    }
592
593    /// Returns the table, column and key values a loc key was generated from, if it can be found in the dependencies.
594    pub fn loc_source(&self, loc_key: &str) -> Option<LocSource> {
595        self.loc_key_source(loc_key).map(|(table, column, key_values)| LocSource { table_name: format!("{table}_tables"), column, key_values })
596    }
597
598    /// Returns the names of the tables in the game files.
599    pub fn dependency_table_names(&self) -> Vec<String> {
600        self.dependencies.vanilla_loose_tables().keys()
601            .chain(self.dependencies.vanilla_tables().keys())
602            .cloned()
603            .collect()
604    }
605
606    /// Returns the version of a table in the game files.
607    ///
608    /// Startpos, twad and CEO tables not in the game files return the latest version in the schema.
609    pub fn dependency_table_version(&self, table_name: &str) -> Result<i32> {
610        if !self.dependencies.is_vanilla_data_loaded(false) {
611            return Err(ApiError::DependenciesNotLoaded.into());
612        }
613
614        if let Some(version) = self.dependencies.db_version(table_name) {
615            return Ok(version);
616        }
617
618        if !(table_name.starts_with("start_pos_") || table_name.starts_with("twad_") || table_name.starts_with("ceo")) {
619            return Err(anyhow!("Table not found in the game files."));
620        }
621
622        loaded_schema(&self.schema)?.definitions_by_table_name(table_name)
623            .and_then(|definitions| definitions.first())
624            .map(|definition| *definition.version())
625            .ok_or_else(|| anyhow!("There are no definitions for this specific table."))
626    }
627
628    /// Returns the tables with the provided name from the game files and the parent packs.
629    pub fn dependency_tables(&self, table_name: &str) -> Result<Vec<RFile>> {
630        Ok(self.dependencies.db_data(table_name, true, true)?.into_iter().cloned().collect())
631    }
632
633    /// Returns the tables of the game files, and the custom tables of the schema, with their version.
634    pub fn dependency_table_versions(&self) -> DependencyTables {
635        let names = self.dependency_table_names().into_iter().chain(self.custom_table_names().unwrap_or_default());
636        let tables = names.filter_map(|name| self.dependency_table_version(&name).ok().map(|version| (name, version))).collect();
637        DependencyTables { tables }
638    }
639
640    /// Returns every decoded table of a type in the game files and the parent packs.
641    pub fn dependency_table_data(&self, table_name: &str) -> Result<DependencyTableData> {
642        let tables = self.dependency_tables(table_name)?.iter()
643            .filter_map(|file| match file.decoded() {
644                Ok(RFileDecoded::DB(table)) => Some(table.clone()),
645                _ => None,
646            })
647            .collect();
648
649        Ok(DependencyTableData { tables })
650    }
651}
652
653impl SessionState {
654
655    /// Returns the definition and row count of a table.
656    pub fn table_info(&mut self, file: &FileRef) -> Result<TableInfo> {
657        let table = self.table(file)?;
658        Ok(TableInfo {
659            table_name: table.name().to_owned(),
660            version: *table.definition().version(),
661            columns: table.definition().fields_processed().to_vec(),
662            row_count: table.len(),
663        })
664    }
665
666    /// Returns a page of the rows of a table matching the request's filters, with only the requested columns.
667    ///
668    /// # Errors
669    ///
670    /// Fails if the table can't be read, or if the request names a column the table doesn't have.
671    pub fn table_rows(&mut self, request: &GetTableRows) -> Result<TableRows> {
672        let table = self.table(&request.file)?;
673        let fields = table.definition().fields_processed();
674        let column_index = |name: &str| fields.iter()
675            .position(|field| field.name() == name)
676            .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {name}.")));
677
678        let columns = match request.columns {
679            Some(ref names) => names.iter()
680                .map(|name| column_index(name).map(|index| (index, name.to_owned())))
681                .collect::<Result<Vec<_>, _>>()?,
682            None => fields.iter().enumerate().map(|(index, field)| (index, field.name().to_owned())).collect(),
683        };
684
685        let filters = request.filters.iter()
686            .map(|filter| column_index(&filter.column).map(|column_index| PreparedFilter::new(filter, column_index)))
687            .collect::<Result<Vec<_>, _>>()?;
688
689        let limit = request.limit.unwrap_or(DEFAULT_ROWS_LIMIT);
690        let mut rows = vec![];
691        let mut total = 0;
692
693        for (index, row) in table.data().iter().enumerate() {
694            if !filters.iter().all(|filter| filter.matches(row)) {
695                continue;
696            }
697
698            if total >= request.offset && rows.len() < limit {
699                let values = columns.iter()
700                    .map(|(column_index, _)| row.get(*column_index).map(decoded_to_json).unwrap_or(Value::Null))
701                    .collect();
702
703                rows.push(TableRow { index, values });
704            }
705
706            total += 1;
707        }
708
709        Ok(TableRows {
710            columns: columns.into_iter().map(|(_, name)| name).collect(),
711            rows,
712            total,
713        })
714    }
715
716    /// Edits rows of a table of an open pack. If any edit fails, none is applied.
717    ///
718    /// # Errors
719    ///
720    /// Fails if the pack doesn't have the table, or if an edit names a column the table doesn't have,
721    /// a row that doesn't exist, or a value that doesn't fit its column.
722    pub fn edit_table(&mut self, request: &EditTable) -> Result<TableEdited> {
723        let file = FileRef { source: FileSource::Pack(request.pack.clone()), path: request.path.clone() };
724        let fields = self.table(&file)?.definition().fields_processed().to_vec();
725        let copied = self.copied_row_values(&request.edits, &fields)?;
726
727        let row_count = match self.decoded_table_file(&file)?.decoded_mut()? {
728            RFileDecoded::DB(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            RFileDecoded::Loc(table) => {
733                let edits = prepare_row_edits(&request.edits, &fields, table.new_row(), table.data().len(), &copied)?;
734                apply_row_edits(table.data_mut(), edits)
735            }
736            _ => return Err(ApiError::NotATable(request.path.clone()).into()),
737        };
738
739        Ok(TableEdited { row_count })
740    }
741
742    /// 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.
743    ///
744    /// # Arguments
745    ///
746    /// * `edits` - The edits, some of which may copy rows.
747    /// * `fields` - Columns of the edited table, as rows see them.
748    ///
749    /// # Errors
750    ///
751    /// 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.
752    fn copied_row_values(&mut self, edits: &[RowEdit], fields: &[Field]) -> Result<BTreeMap<usize, Vec<(usize, DecodedData)>>> {
753        let mut copied = BTreeMap::new();
754        for (edit_index, edit) in edits.iter().enumerate() {
755            let RowEdit::Insert { copy_from: Some(row_ref), .. } = edit else {
756                continue;
757            };
758
759            let invalid = |message: String| ApiError::InvalidParams(format!("Edit {edit_index}: {message}"));
760            let table = self.table(&row_ref.file)?;
761            let source_fields = table.definition().fields_processed();
762            let data = table.data();
763            let row = data.get(row_ref.index)
764                .ok_or_else(|| invalid(format!("row {} of {} doesn't exist.", row_ref.index, row_ref.file.path)))?;
765
766            let values = fields.iter().enumerate()
767                .filter_map(|(column_index, field)| {
768                    let source_index = source_fields.iter().position(|source_field| source_field.name() == field.name())?;
769                    Some(copied_value(field.field_type(), &row[source_index])
770                        .map(|value| (column_index, value))
771                        .map_err(|_| invalid(format!("the value of the column {} can't be copied, as its type is different.", field.name()))))
772                })
773                .collect::<Result<Vec<_>, _>>()?;
774
775            copied.insert(edit_index, values);
776        }
777
778        Ok(copied)
779    }
780
781    /// Merges tables of the same type of a pack into a new one.
782    ///
783    /// # Returns
784    ///
785    /// The path of the merged table, or the conflicts that prevented the merge.
786    pub fn merge_tables(&mut self, request: &MergeTables) -> Result<TablesMerged> {
787        let paths = self.pack_container_paths(&request.pack, &request.paths)?;
788        let mut options = MergeOptions::default();
789        options.set_delta_merge(request.delta);
790        options.set_resolutions(request.resolutions.clone());
791
792        Ok(match self.merge_files(&request.pack, &paths, &request.merged_path, request.delete_sources, &options)? {
793            MergeOutcome::Merged(path) => TablesMerged { merged: Some(path), conflicts: vec![] },
794            MergeOutcome::Conflicts(conflicts) => TablesMerged { merged: None, conflicts },
795        })
796    }
797
798    /// Updates a table of a pack to the version it has in the game files.
799    pub fn upgrade_table(&mut self, request: &UpgradeTable) -> Result<TableUpgraded> {
800        let (old_version, new_version, deleted_columns, added_columns) = self.update_table(&request.pack, &ContainerPath::File(request.path.clone()))?;
801        Ok(TableUpgraded { old_version, new_version, deleted_columns, added_columns })
802    }
803
804    /// Changes a value of a key column in every table of a pack, including the columns referencing it.
805    ///
806    /// # Errors
807    ///
808    /// Fails if the table has no definition, or no column with the provided name.
809    pub fn rename_key(&mut self, request: &RenameKey) -> Result<FilesEdited> {
810        let version = match request.version {
811            Some(version) => version,
812            None => self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version,
813        };
814
815        let definition = loaded_schema(&self.schema)?.definition_by_name_and_version(&request.table_name, version)
816            .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?
817            .clone();
818
819        let field = definition.fields_processed().iter()
820            .find(|field| field.name() == request.column)
821            .cloned()
822            .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
823
824        let changes = [(field, request.old_value.clone(), request.new_value.clone())];
825        let (edited, _) = self.cascade_edition(&request.pack, &request.table_name, &definition, &changes)?;
826        Ok(FilesEdited { edited: edited.iter().map(|path| path.path_raw().to_owned()).collect() })
827    }
828
829    /// Adds rows to a key deletes table of a pack, one per key.
830    ///
831    /// # Errors
832    ///
833    /// Fails if the key deletes table doesn't exist in the pack.
834    pub fn add_key_deletes(&mut self, request: &AddKeyDeletes) -> Result<FilesEdited> {
835        let keys = request.keys.iter().cloned().collect::<HashSet<_>>();
836        let path = self.add_keys_to_key_deletes(&request.pack, &request.file_name, &request.table_name, &keys)?
837            .ok_or_else(|| ApiError::FileNotFound(format!("db/{KEY_DELETES_TABLE_NAME}/{}", request.file_name)))?;
838
839        Ok(FilesEdited { edited: vec![path.path_raw().to_owned()] })
840    }
841
842    /// Writes a table from any source, except the Assembly Kit, to a TSV file.
843    ///
844    /// # Arguments
845    ///
846    /// * `request` - Table to export, and where.
847    /// * `keys_first` - If the TSV uses the old column order, with keys first.
848    pub fn export_table_tsv(&mut self, request: &ExportTsv, keys_first: bool) -> Result<()> {
849        let (pack_key, data_source) = match request.file.source {
850            FileSource::Pack(ref pack_key) => (pack_key.as_str(), DataSource::PackFile),
851            FileSource::GameFiles => ("", DataSource::GameFiles),
852            FileSource::ParentFiles => ("", DataSource::ParentFiles),
853            FileSource::AssemblyKit => return Err(ApiError::InvalidParams("Assembly Kit tables can't be exported to TSV.".to_owned()).into()),
854        };
855
856        self.export_tsv(pack_key, &request.file.path, &request.destination, data_source, keys_first)
857    }
858
859    /// Replaces a table of a pack with the contents of a TSV file, keeping its GUID.
860    pub fn import_table_tsv(&mut self, request: &ImportTsv) -> Result<TableEdited> {
861        let row_count = match self.import_tsv(&request.pack, &request.path, &request.tsv_path)? {
862            RFileDecoded::DB(table) => table.len(),
863            RFileDecoded::Loc(table) => table.len(),
864            _ => return Err(ApiError::NotATable(request.path.clone()).into()),
865        };
866
867        Ok(TableEdited { row_count })
868    }
869
870    /// Returns a DB or Loc table from any source, decoding it first if needed.
871    fn table(&mut self, file: &FileRef) -> Result<&TableInMemory> {
872        self.check_file_exists(file)?;
873
874        if file.source == FileSource::AssemblyKit {
875            let not_found = || ApiError::FileNotFound(file.path.clone());
876            let table_name = file.path.split('/').nth(1).ok_or_else(not_found)?;
877            let table = self.dependencies.asskit_only_db_tables().get(table_name).ok_or_else(not_found)?;
878            return Ok(table.table());
879        }
880
881        match self.decoded_table_file(file)?.decoded()? {
882            RFileDecoded::DB(table) => Ok(table.table()),
883            RFileDecoded::Loc(table) => Ok(table.table()),
884            _ => Err(ApiError::NotATable(file.path.clone()).into()),
885        }
886    }
887
888    /// Returns a DB or Loc file from an open pack or the dependencies, decoded.
889    fn decoded_table_file(&mut self, file: &FileRef) -> Result<&mut RFile> {
890        let rfile = match file.source {
891            FileSource::Pack(ref pack_key) => pack_mut(&mut self.packs, pack_key)?.files_mut().get_mut(&file.path),
892            FileSource::GameFiles => self.dependencies.file_mut(&file.path, true, false).ok(),
893            FileSource::ParentFiles => self.dependencies.file_mut(&file.path, false, true).ok(),
894
895            // Assembly Kit tables are kept decoded, outside of any file.
896            FileSource::AssemblyKit => return Err(ApiError::ReadOnly(file.path.clone()).into()),
897        }.ok_or_else(|| ApiError::FileNotFound(file.path.clone()))?;
898
899        let file_type = rfile.file_type();
900        if file_type != FileType::DB && file_type != FileType::Loc {
901            return Err(ApiError::NotATable(file.path.clone()).into());
902        }
903
904        if file_type == FileType::DB && self.schema.is_none() {
905            return Err(ApiError::SchemaNotLoaded.into());
906        }
907
908        let mut extra_data = DecodeableExtraData::default();
909        extra_data.set_schema(self.schema.as_ref());
910        rfile.decode(&Some(extra_data), true, false)?;
911        Ok(rfile)
912    }
913}
914
915impl PreparedFilter {
916
917    /// Prepares a filter to check rows, with the index of its column.
918    fn new(filter: &RowFilter, column_index: usize) -> Self {
919        Self {
920            column_index,
921            op: filter.op,
922            value: if filter.ignore_case { filter.value.to_lowercase() } else { filter.value.clone() },
923            ignore_case: filter.ignore_case,
924        }
925    }
926
927    /// Returns if the value of the filter's column in a row matches the filter.
928    fn matches(&self, row: &[DecodedData]) -> bool {
929        let Some(data) = row.get(self.column_index) else { return false };
930        let value = data.data_to_string();
931        let value = if self.ignore_case { Cow::Owned(value.to_lowercase()) } else { value };
932
933        match self.op {
934            FilterOp::Equals => *value == self.value,
935            FilterOp::NotEquals => *value != self.value,
936            FilterOp::Contains => value.contains(&self.value),
937            FilterOp::StartsWith => value.starts_with(&self.value),
938            FilterOp::EndsWith => value.ends_with(&self.value),
939        }
940    }
941}
942
943/// 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.
944///
945/// # Arguments
946///
947/// * `edits` - The edits to prepare.
948/// * `fields` - Columns of the table, as rows see them.
949/// * `new_row` - A row with the default value of each column.
950/// * `row_count` - Amount of rows of the table before the edits.
951/// * `copied` - Values of the rows the inserts copy, by edit index and column index.
952fn 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> {
953    let mut prepared = Vec::with_capacity(edits.len());
954    for (edit_index, edit) in edits.iter().enumerate() {
955        let invalid = |message: String| ApiError::InvalidParams(format!("Edit {edit_index}: {message}"));
956        let missing_row = |index: usize, row_count: usize| invalid(format!("row {index} doesn't exist, the table has {row_count} rows at this point."));
957
958        match edit {
959            RowEdit::Insert { index, values, .. } => {
960                let index = index.unwrap_or(row_count);
961                if index > row_count {
962                    return Err(missing_row(index, row_count));
963                }
964
965                let mut row = new_row.clone();
966                let copied_values = copied.get(&edit_index).into_iter().flatten().cloned();
967                for (column_index, value) in copied_values.chain(row_values(fields, values).map_err(invalid)?) {
968                    row[column_index] = value;
969                }
970
971                prepared.push(PreparedEdit::Insert(index, row));
972                row_count += 1;
973            }
974            RowEdit::Update { index, values } => {
975                if *index >= row_count {
976                    return Err(missing_row(*index, row_count));
977                }
978
979                prepared.push(PreparedEdit::Update(*index, row_values(fields, values).map_err(invalid)?));
980            }
981            RowEdit::Delete { indexes } => {
982                let mut indexes = indexes.clone();
983                indexes.sort_unstable_by(|a, b| b.cmp(a));
984                indexes.dedup();
985
986                if let Some(index) = indexes.first().filter(|index| **index >= row_count) {
987                    return Err(missing_row(*index, row_count));
988                }
989
990                row_count -= indexes.len();
991                prepared.push(PreparedEdit::Delete(indexes));
992            }
993        }
994    }
995
996    Ok(prepared)
997}
998
999/// Applies prepared row edits to the rows of a table.
1000///
1001/// # Returns
1002///
1003/// The amount of rows after the edits.
1004fn apply_row_edits(rows: &mut Vec<Vec<DecodedData>>, edits: Vec<PreparedEdit>) -> usize {
1005    for edit in edits {
1006        match edit {
1007            PreparedEdit::Insert(index, row) => rows.insert(index, row),
1008            PreparedEdit::Update(index, values) => {
1009                for (column_index, value) in values {
1010                    rows[index][column_index] = value;
1011                }
1012            }
1013            PreparedEdit::Delete(indexes) => {
1014                for index in indexes {
1015                    rows.remove(index);
1016                }
1017            }
1018        }
1019    }
1020
1021    rows.len()
1022}
1023
1024/// Converts values given by column name to the types of their columns.
1025///
1026/// # Returns
1027///
1028/// The index of the column of each value, and the converted value.
1029fn row_values(fields: &[Field], values: &BTreeMap<String, Value>) -> Result<Vec<(usize, DecodedData)>, String> {
1030    values.iter()
1031        .map(|(column, value)| {
1032            let column_index = fields.iter()
1033                .position(|field| field.name() == column)
1034                .ok_or_else(|| format!("the table has no column named {column}."))?;
1035
1036            let value = json_to_decoded(fields[column_index].field_type(), value)
1037                .map_err(|error| format!("invalid value for column {column}: {error}"))?;
1038
1039            Ok((column_index, value))
1040        })
1041        .collect()
1042}
1043
1044/// Returns a copy of a table value for a column of the provided type, converting it if it's of another type.
1045fn copied_value(field_type: &FieldType, value: &DecodedData) -> Result<DecodedData> {
1046    if value.is_field_type_correct(field_type) {
1047        Ok(value.clone())
1048    } else {
1049        Ok(DecodedData::new_from_type_and_string(field_type, &value.data_to_string())?)
1050    }
1051}
1052
1053/// Converts a JSON value to a table value of the provided type.
1054///
1055/// Booleans, numbers and strings are accepted, as long as their text parses as the type.
1056fn json_to_decoded(field_type: &FieldType, value: &Value) -> Result<DecodedData> {
1057    let text = match value {
1058        Value::String(text) => Cow::Borrowed(text.as_str()),
1059        Value::Bool(_) | Value::Number(_) => Cow::Owned(value.to_string()),
1060        Value::Null | Value::Array(_) | Value::Object(_) => return Err(anyhow!("expected a boolean, number or string, found {value}.")),
1061    };
1062
1063    DecodedData::new_from_type_and_string(field_type, &text)
1064        .map_err(|_| anyhow!("expected {}, found {value}.", expected_value(field_type)))
1065}
1066
1067/// Returns how the values a column of the provided type accepts are described in errors.
1068fn expected_value(field_type: &FieldType) -> &'static str {
1069    match field_type {
1070        FieldType::Boolean => "a boolean",
1071        FieldType::F32 | FieldType::F64 => "a number",
1072        FieldType::I16 | FieldType::OptionalI16 => "a 16-bit integer",
1073        FieldType::I32 | FieldType::OptionalI32 => "a 32-bit integer",
1074        FieldType::I64 | FieldType::OptionalI64 => "a 64-bit integer",
1075        FieldType::ColourRGB => "a hexadecimal colour, like FF0000",
1076        FieldType::StringU8 |
1077        FieldType::StringU16 |
1078        FieldType::OptionalStringU8 |
1079        FieldType::OptionalStringU16 |
1080        FieldType::SequenceU16(_) |
1081        FieldType::SequenceU32(_) => "a string",
1082    }
1083}
1084
1085/// Returns a table value as JSON: booleans and numbers as themselves, everything else as text.
1086///
1087/// Floats not representable in JSON (NaN, infinite) become `null`.
1088fn decoded_to_json(data: &DecodedData) -> Value {
1089    match data {
1090        DecodedData::Boolean(value) => Value::Bool(*value),
1091
1092        // Parsing the f32's shortest text form keeps values like 0.1 exact, instead of 0.10000000149011612.
1093        DecodedData::F32(value) => value.to_string().parse::<f64>().ok().and_then(Number::from_f64).map_or(Value::Null, Value::Number),
1094        DecodedData::F64(value) => Number::from_f64(*value).map_or(Value::Null, Value::Number),
1095        DecodedData::I16(value) | DecodedData::OptionalI16(value) => Value::from(*value),
1096        DecodedData::I32(value) | DecodedData::OptionalI32(value) => Value::from(*value),
1097        DecodedData::I64(value) | DecodedData::OptionalI64(value) => Value::from(*value),
1098        DecodedData::ColourRGB(value) |
1099        DecodedData::StringU8(value) |
1100        DecodedData::StringU16(value) |
1101        DecodedData::OptionalStringU8(value) |
1102        DecodedData::OptionalStringU16(value) => Value::String(value.clone()),
1103        DecodedData::SequenceU16(_) |
1104        DecodedData::SequenceU32(_) => Value::String(data.data_to_string().into_owned()),
1105    }
1106}
1107
1108/// Finds the first row with a value in a column of a table.
1109///
1110/// If the column is localised, it's not in the table's data, so the first key column is searched instead.
1111///
1112/// # Returns
1113///
1114/// The column and row indexes of the row.
1115fn find_in_db(table: &DB, column_name: &str, value: &str) -> Option<(usize, usize)> {
1116    let definition = table.definition();
1117    let key_column_name = if definition.localised_fields().iter().any(|field| field.name() == column_name) {
1118        definition.localised_key_order().first()
1119            .and_then(|index| definition.fields_processed().get(*index as usize).map(|field| field.name().to_owned()))
1120    } else {
1121        None
1122    };
1123
1124    let column_name = key_column_name.as_deref().unwrap_or(column_name);
1125    let (column_index, row_indexes) = table.table().rows_containing_data(column_name, value)?;
1126    row_indexes.first().map(|row_index| (column_index, *row_index))
1127}
1128
1129/// Finds the first row with a value in a column, among DB files.
1130fn find_in_db_files(files: &[&RFile], column_name: &str, value: &str, source: &FileSource) -> Option<FoundRow> {
1131    files.iter().find_map(|file| match file.decoded() {
1132        Ok(RFileDecoded::DB(table)) => find_in_db(table, column_name, value)
1133            .map(|(column_index, row_index)| FoundRow {
1134                location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index },
1135                values: row_values_by_column(table.table(), row_index),
1136            }),
1137        _ => None,
1138    })
1139}
1140
1141/// Finds the first row with a key, among Loc files.
1142fn find_in_loc_files(files: &[&RFile], loc_key: &str, source: &FileSource) -> Option<FoundRow> {
1143    files.iter().find_map(|file| match file.decoded() {
1144        Ok(RFileDecoded::Loc(table)) => {
1145            let (column_index, row_indexes) = table.table().rows_containing_data("key", loc_key)?;
1146            row_indexes.first().map(|row_index| FoundRow {
1147                location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index: *row_index },
1148                values: row_values_by_column(table.table(), *row_index),
1149            })
1150        }
1151        _ => None,
1152    })
1153}
1154
1155/// Returns the values of a row of a table, by column name.
1156fn row_values_by_column(table: &TableInMemory, row_index: usize) -> BTreeMap<String, Value> {
1157    let data = table.data();
1158    let Some(row) = data.get(row_index) else {
1159        return BTreeMap::new();
1160    };
1161
1162    table.definition().fields_processed().iter()
1163        .zip(row)
1164        .map(|(field, value)| (field.name().to_owned(), decoded_to_json(value)))
1165        .collect()
1166}
1167
1168/// Returns every row of a DB file with a value in any of the provided columns.
1169fn references_in_file(file: &RFile, columns: &[String], value: &str, source: &FileSource) -> Vec<Usage> {
1170    let Ok(RFileDecoded::DB(table)) = file.decoded() else { return vec![] };
1171
1172    columns.iter()
1173        .filter_map(|column_name| table.table().rows_containing_data(column_name, value).map(|found| (column_name, found)))
1174        .flat_map(|(column_name, (column_index, row_indexes))| row_indexes.into_iter()
1175            .map(|row_index| Usage {
1176                location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index },
1177                column: column_name.to_owned(),
1178                values: row_values_by_column(table.table(), row_index),
1179            })
1180            .collect::<Vec<_>>())
1181        .collect()
1182}
1183
1184/// Delta-merges the decoded DB or Loc `sources` (same type/table) against the vanilla/parent baseline,
1185/// applying any already-known `resolutions`. See [`rpfm_extensions::merge`] for the merge rules.
1186fn delta_merge_files(sources: &[&RFile], merged_path: &str, dependencies: &Dependencies, resolutions: &[MergeResolution]) -> Result<DeltaMergeOutcome> {
1187    if sources.len() < 2 {
1188        return Err(anyhow!("Not enough tables provided to merge."));
1189    }
1190
1191    let (merged, conflicts) = match sources[0].decoded()? {
1192        RFileDecoded::DB(_) => {
1193            let tables = sources.iter()
1194                .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some((file.path_in_container_raw(), table)) } else { None })
1195                .collect::<Vec<_>>();
1196
1197            let baseline = db_baseline(dependencies, tables[0].1.table_name());
1198            let (merged, conflicts) = delta_merge_db(&tables, baseline.as_ref(), resolutions)?;
1199            (RFileDecoded::DB(merged), conflicts)
1200        },
1201        RFileDecoded::Loc(_) => {
1202            let tables = sources.iter()
1203                .filter_map(|file| if let Ok(RFileDecoded::Loc(table)) = file.decoded() { Some((file.path_in_container_raw(), table)) } else { None })
1204                .collect::<Vec<_>>();
1205
1206            let baseline = loc_baseline(dependencies);
1207            let (merged, conflicts) = delta_merge_loc(&tables, baseline.as_ref(), resolutions)?;
1208            (RFileDecoded::Loc(merged), conflicts)
1209        },
1210        _ => return Err(anyhow!("Delta merge is only supported for DB and Loc tables.")),
1211    };
1212
1213    if conflicts.is_empty() {
1214        Ok(DeltaMergeOutcome::Merged(RFile::new_from_decoded(&merged, current_time()?, merged_path)))
1215    } else {
1216        Ok(DeltaMergeOutcome::Conflicts(conflicts))
1217    }
1218}
1219
1220//-------------------------------------------------------------------------------//
1221//                                   Tests
1222//-------------------------------------------------------------------------------//
1223
1224#[cfg(test)]
1225#[path = "tables_test.rs"]
1226mod tables_test;