1use std::collections::HashSet;
4
5use crate::encoding;
6use crate::file::{FileError, PageReader};
7use crate::format::{ColumnType, JetFormat, PageType, MAX_INDEX_COLUMNS};
8use crate::map;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum IndexColumnOrder {
17 Ascending,
18 Descending,
19}
20
21#[derive(Debug, Clone)]
23pub struct IndexColumn {
24 pub col_num: u16,
26 pub order: IndexColumnOrder,
28}
29
30#[derive(Debug, Clone)]
32pub struct ForeignKeyReference {
33 pub fk_index_type: u8,
35 pub fk_index_number: u32,
37 pub fk_table_page: u32,
39 pub update_action: u8,
41 pub delete_action: u8,
43}
44
45#[derive(Debug, Clone)]
47pub struct IndexDef {
48 pub name: String,
50 pub index_num: u16,
52 pub index_type: u8,
54 pub columns: Vec<IndexColumn>,
56 pub flags: u8,
58 pub first_data_page: u32,
60 pub foreign_key: Option<ForeignKeyReference>,
62}
63
64#[derive(Debug, Clone)]
66pub struct ColumnDef {
67 pub name: String,
68 pub col_type: ColumnType,
69 pub col_num: u16,
70 pub var_col_num: u16,
71 pub fixed_offset: u16,
72 pub col_size: u16,
73 pub flags: u8,
74 pub is_fixed: bool,
75 pub scale: u8,
77 pub precision: u8,
79}
80
81#[derive(Debug, Clone)]
83pub struct TableDef {
84 pub name: String,
85 pub num_rows: u32,
86 pub num_cols: u16,
87 pub num_var_cols: u16,
88 pub columns: Vec<ColumnDef>,
89 pub indexes: Vec<IndexDef>,
90 pub data_pages: Vec<u32>,
91}
92
93pub fn is_replication_column(col: &ColumnDef) -> bool {
95 (col.flags & crate::format::column_flags::REPLICATION) != 0
96}
97
98type PhysicalIndexEntry = (Vec<IndexColumn>, u8, u32);
104
105struct LogicalIndex {
107 index_num: u16,
108 index_col_entry: u32,
109 fk_index_type: u8,
110 fk_index_number: u32,
111 fk_table_page: u32,
112 update_action: u8,
113 delete_action: u8,
114 index_type: u8,
115}
116
117pub fn read_table_def(
126 reader: &mut PageReader,
127 name: &str,
128 tdef_page: u32,
129) -> Result<TableDef, FileError> {
130 let is_jet3 = reader.header().version.is_jet3();
131
132 let tdef_buf = build_tdef_buffer(reader, tdef_page)?;
134
135 let format = reader.format();
136 let cursor = &mut TdefCursor::new(&tdef_buf, 0);
137
138 let num_rows = cursor.u32_le_at(format.tdef_row_count_pos)?;
140 let num_var_cols = cursor.u16_le_at(format.tdef_var_col_count_pos)?;
141 let num_cols = cursor.u16_le_at(format.tdef_column_count_pos)?;
142 let num_idxs = cursor.u32_le_at(format.tdef_index_count_pos)?;
143 let num_real_idxs = cursor.u32_le_at(format.tdef_real_index_count_pos)?;
144
145 let pg_row = cursor.u32_le_at(format.tdef_owned_pages_pos)?;
147 let data_pages = if pg_row != 0 {
148 let map_data = reader.read_pg_row(pg_row)?;
149 map::collect_page_numbers(reader, &map_data)?
150 } else {
151 Vec::new()
152 };
153
154 let col_entry_start =
156 format.tdef_index_entries_pos + (num_real_idxs as usize) * format.tdef_index_entry_span;
157 cursor.set_position(col_entry_start);
158 let mut columns = parse_column_entries(
159 cursor,
160 format.tdef_column_entry_span,
161 num_cols as usize,
162 is_jet3,
163 format,
164 )?;
165
166 let col_names = read_names(cursor, num_cols as usize, is_jet3)?;
168 for (col, col_name) in columns.iter_mut().zip(col_names) {
169 col.name = col_name;
170 }
171
172 let mut idx_col_defs = parse_index_column_defs(cursor, num_real_idxs, format)?;
174
175 let logical_indexes = parse_logical_indexes(cursor, num_idxs, format)?;
177
178 let non_fk_count = logical_indexes
180 .iter()
181 .filter(|li| li.index_type != crate::format::index_type::FOREIGN_KEY)
182 .count();
183 if non_fk_count != idx_col_defs.len() {
184 idx_col_defs.truncate(non_fk_count);
185 }
186
187 let idx_names = read_names(cursor, num_idxs as usize, is_jet3)?;
189
190 let indexes = build_index_defs(&logical_indexes, &idx_col_defs, idx_names);
192
193 columns.sort_by_key(|c| c.col_num);
195
196 Ok(TableDef {
197 name: name.to_string(),
198 num_rows,
199 num_cols,
200 num_var_cols,
201 columns,
202 indexes,
203 data_pages,
204 })
205}
206
207fn build_tdef_buffer(reader: &mut PageReader, tdef_page: u32) -> Result<Vec<u8>, FileError> {
213 let first_page = reader.read_page_copy(tdef_page)?;
214
215 if first_page.is_empty() || first_page[0] != PageType::TableDefinition as u8 {
217 return Err(FileError::InvalidTableDef {
218 reason: "first page is not a TableDefinition page",
219 });
220 }
221
222 let mut next = u32::from_le_bytes([first_page[4], first_page[5], first_page[6], first_page[7]]);
224 let mut buf = first_page;
225
226 let mut visited = HashSet::new();
228 while next != 0 {
229 if !visited.insert(next) {
230 return Err(FileError::InvalidTableDef {
231 reason: "circular page reference in TDEF chain",
232 });
233 }
234 let cont_page = reader.read_page_copy(next)?;
235 if cont_page.len() > 8 {
236 buf.extend_from_slice(&cont_page[8..]);
237 }
238 next = u32::from_le_bytes([cont_page[4], cont_page[5], cont_page[6], cont_page[7]]);
239 }
240
241 Ok(buf)
242}
243
244struct TdefCursor<'a> {
249 buf: &'a [u8],
250 pos: usize,
251}
252
253impl<'a> TdefCursor<'a> {
254 fn new(buf: &'a [u8], pos: usize) -> Self {
255 Self { buf, pos }
256 }
257
258 fn position(&self) -> usize {
259 self.pos
260 }
261
262 fn set_position(&mut self, pos: usize) {
263 self.pos = pos;
264 }
265
266 fn read_u8(&mut self) -> Result<u8, FileError> {
269 if self.pos >= self.buf.len() {
270 return Err(FileError::InvalidTableDef {
271 reason: "unexpected end of TDEF buffer",
272 });
273 }
274 let v = self.buf[self.pos];
275 self.pos += 1;
276 Ok(v)
277 }
278
279 fn read_u16_le(&mut self) -> Result<u16, FileError> {
280 if self.pos + 2 > self.buf.len() {
281 return Err(FileError::InvalidTableDef {
282 reason: "unexpected end of TDEF buffer",
283 });
284 }
285 let v = u16::from_le_bytes([self.buf[self.pos], self.buf[self.pos + 1]]);
286 self.pos += 2;
287 Ok(v)
288 }
289
290 fn read_u32_le(&mut self) -> Result<u32, FileError> {
291 if self.pos + 4 > self.buf.len() {
292 return Err(FileError::InvalidTableDef {
293 reason: "unexpected end of TDEF buffer",
294 });
295 }
296 let v = u32::from_le_bytes([
297 self.buf[self.pos],
298 self.buf[self.pos + 1],
299 self.buf[self.pos + 2],
300 self.buf[self.pos + 3],
301 ]);
302 self.pos += 4;
303 Ok(v)
304 }
305
306 fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], FileError> {
307 if self.pos + n > self.buf.len() {
308 return Err(FileError::InvalidTableDef {
309 reason: "unexpected end of TDEF buffer",
310 });
311 }
312 let slice = &self.buf[self.pos..self.pos + n];
313 self.pos += n;
314 Ok(slice)
315 }
316
317 fn skip(&mut self, n: usize) -> Result<(), FileError> {
318 if self.pos + n > self.buf.len() {
319 return Err(FileError::InvalidTableDef {
320 reason: "unexpected end of TDEF buffer",
321 });
322 }
323 self.pos += n;
324 Ok(())
325 }
326
327 fn u8_at(&self, pos: usize) -> Result<u8, FileError> {
330 if pos >= self.buf.len() {
331 return Err(FileError::InvalidTableDef {
332 reason: "unexpected end of TDEF buffer",
333 });
334 }
335 Ok(self.buf[pos])
336 }
337
338 fn u16_le_at(&self, pos: usize) -> Result<u16, FileError> {
339 if pos + 2 > self.buf.len() {
340 return Err(FileError::InvalidTableDef {
341 reason: "unexpected end of TDEF buffer",
342 });
343 }
344 Ok(u16::from_le_bytes([self.buf[pos], self.buf[pos + 1]]))
345 }
346
347 fn u32_le_at(&self, pos: usize) -> Result<u32, FileError> {
348 if pos + 4 > self.buf.len() {
349 return Err(FileError::InvalidTableDef {
350 reason: "unexpected end of TDEF buffer",
351 });
352 }
353 Ok(u32::from_le_bytes([
354 self.buf[pos],
355 self.buf[pos + 1],
356 self.buf[pos + 2],
357 self.buf[pos + 3],
358 ]))
359 }
360}
361
362fn read_names(
366 cursor: &mut TdefCursor,
367 count: usize,
368 is_jet3: bool,
369) -> Result<Vec<String>, FileError> {
370 let mut names = Vec::with_capacity(count);
371 for _ in 0..count {
372 if is_jet3 {
373 let name_len = cursor.read_u8()? as usize;
374 let bytes = cursor.read_bytes(name_len)?;
375 names.push(encoding::decode_latin1(bytes));
376 } else {
377 let name_len = cursor.read_u16_le()? as usize;
378 let bytes = cursor.read_bytes(name_len)?;
379 names.push(encoding::decode_utf16le(bytes).map_err(|_| {
380 FileError::InvalidTableDef {
381 reason: "invalid UTF-16LE name",
382 }
383 })?);
384 }
385 }
386 Ok(names)
387}
388
389fn parse_column_entries(
391 cursor: &mut TdefCursor,
392 span: usize,
393 count: usize,
394 is_jet3: bool,
395 format: &JetFormat,
396) -> Result<Vec<ColumnDef>, FileError> {
397 let mut columns = Vec::with_capacity(count);
398 for _ in 0..count {
399 let entry_start = cursor.position();
400
401 let col_type = ColumnType::try_from(cursor.u8_at(entry_start)?)?;
402
403 let (col_num, var_col_num) = if is_jet3 {
404 (
405 cursor.u8_at(entry_start + format.coldef_number_pos)? as u16,
406 cursor.u16_le_at(entry_start + format.coldef_var_col_index_pos)?,
407 )
408 } else {
409 (
410 cursor.u16_le_at(entry_start + format.coldef_number_pos)?,
411 cursor.u16_le_at(entry_start + format.coldef_var_col_index_pos)?,
412 )
413 };
414
415 let flags = cursor.u8_at(entry_start + format.coldef_flags_pos)?;
416 let is_fixed = (flags & crate::format::column_flags::FIXED) != 0;
417 let fixed_offset = cursor.u16_le_at(entry_start + format.coldef_fixed_data_pos)?;
418 let col_size = cursor.u16_le_at(entry_start + format.coldef_length_pos)?;
419 let scale = cursor.u8_at(entry_start + format.coldef_scale_pos)?;
420 let precision = cursor.u8_at(entry_start + format.coldef_precision_pos)?;
421
422 columns.push(ColumnDef {
423 name: String::new(), col_type,
425 col_num,
426 var_col_num,
427 fixed_offset,
428 col_size,
429 flags,
430 is_fixed,
431 scale,
432 precision,
433 });
434
435 cursor.set_position(entry_start + span);
436 }
437 Ok(columns)
438}
439
440fn parse_index_column_defs(
442 cursor: &mut TdefCursor,
443 count: u32,
444 format: &JetFormat,
445) -> Result<Vec<PhysicalIndexEntry>, FileError> {
446 let mut idx_col_defs = Vec::with_capacity(count as usize);
447
448 for _ in 0..count {
449 cursor.skip(format.idx_col_skip_before)?;
450
451 let mut idx_columns = Vec::new();
452 for _ in 0..MAX_INDEX_COLUMNS {
453 let col_id = cursor.read_u16_le()?;
454 let order_flag = cursor.read_u8()?;
455
456 if col_id != 0xFFFF {
457 let order = if order_flag == 0x01 {
458 IndexColumnOrder::Ascending
459 } else {
460 IndexColumnOrder::Descending
461 };
462 idx_columns.push(IndexColumn {
463 col_num: col_id,
464 order,
465 });
466 }
467 }
468
469 cursor.skip(4)?; let first_pg = cursor.read_u32_le()?;
471 cursor.skip(format.idx_col_skip_before_flags)?;
472 let idx_flags = cursor.read_u8()?;
473 cursor.skip(format.idx_col_skip_after_flags)?;
474
475 idx_col_defs.push((idx_columns, idx_flags, first_pg));
476 }
477
478 Ok(idx_col_defs)
479}
480
481fn parse_logical_indexes(
483 cursor: &mut TdefCursor,
484 count: u32,
485 format: &JetFormat,
486) -> Result<Vec<LogicalIndex>, FileError> {
487 let mut logical_indexes = Vec::with_capacity(count as usize);
488
489 for _ in 0..count {
490 let entry_start = cursor.position();
491
492 cursor.skip(format.idx_info_skip_before)?;
493 let index_num = cursor.read_u16_le()?;
494 cursor.skip(2)?; let index_col_entry = cursor.read_u32_le()?;
496 let fk_index_type = cursor.read_u8()?;
497 let fk_index_number = cursor.read_u32_le()?;
498 let fk_table_page = cursor.read_u32_le()?;
499 let update_action = cursor.read_u8()?;
500 let delete_action = cursor.read_u8()?;
501 let index_type = cursor.u8_at(entry_start + format.idx_info_type_offset)?;
502
503 logical_indexes.push(LogicalIndex {
504 index_num,
505 index_col_entry,
506 fk_index_type,
507 fk_index_number,
508 fk_table_page,
509 update_action,
510 delete_action,
511 index_type,
512 });
513
514 cursor.set_position(entry_start + format.idx_info_block_size);
515 }
516
517 Ok(logical_indexes)
518}
519
520fn build_index_defs(
522 logical_indexes: &[LogicalIndex],
523 idx_col_defs: &[PhysicalIndexEntry],
524 idx_names: Vec<String>,
525) -> Vec<IndexDef> {
526 let mut indexes = Vec::with_capacity(logical_indexes.len());
527 for (i, logical) in logical_indexes.iter().enumerate() {
528 let name = idx_names.get(i).cloned().unwrap_or_default();
529
530 if logical.index_type == crate::format::index_type::FOREIGN_KEY {
531 indexes.push(IndexDef {
532 name,
533 index_num: logical.index_num,
534 index_type: logical.index_type,
535 columns: Vec::new(),
536 flags: 0,
537 first_data_page: 0,
538 foreign_key: Some(ForeignKeyReference {
539 fk_index_type: logical.fk_index_type,
540 fk_index_number: logical.fk_index_number,
541 fk_table_page: logical.fk_table_page,
542 update_action: logical.update_action,
543 delete_action: logical.delete_action,
544 }),
545 });
546 } else {
547 let col_entry_idx = logical.index_col_entry as usize;
548 let (cols, flags, first_pg) = if col_entry_idx < idx_col_defs.len() {
549 idx_col_defs[col_entry_idx].clone()
550 } else {
551 log::warn!(
552 "index '{}': column entry index {} out of range (max {})",
553 name,
554 col_entry_idx,
555 idx_col_defs.len()
556 );
557 (Vec::new(), 0, 0)
558 };
559 indexes.push(IndexDef {
560 name,
561 index_num: logical.index_num,
562 index_type: logical.index_type,
563 columns: cols,
564 flags,
565 first_data_page: first_pg,
566 foreign_key: None,
567 });
568 }
569 }
570 indexes
571}
572
573#[cfg(test)]
578mod tests {
579 use super::*;
580 use crate::format::ColumnType;
581 use crate::format::{column_flags, CATALOG_PAGE};
582
583 fn test_data_path(relative: &str) -> Option<std::path::PathBuf> {
584 let manifest_dir = env!("CARGO_MANIFEST_DIR");
585 let path = std::path::PathBuf::from(manifest_dir)
586 .join("../../testdata")
587 .join(relative);
588 if path.exists() {
589 Some(path)
590 } else {
591 None
592 }
593 }
594
595 macro_rules! skip_if_missing {
596 ($path:expr) => {
597 match test_data_path($path) {
598 Some(p) => p,
599 None => {
600 eprintln!("SKIP: test data not found: {}", $path);
601 return;
602 }
603 }
604 };
605 }
606
607 fn assert_msysobjects(tdef: &TableDef) {
608 assert!(
609 tdef.num_cols > 0,
610 "MSysObjects should have at least one column"
611 );
612
613 let col_names: Vec<&str> = tdef.columns.iter().map(|c| c.name.as_str()).collect();
614 assert!(
615 col_names.contains(&"Id"),
616 "MSysObjects should have 'Id' column, found: {col_names:?}"
617 );
618 assert!(
619 col_names.contains(&"Name"),
620 "MSysObjects should have 'Name' column, found: {col_names:?}"
621 );
622 assert!(
623 col_names.contains(&"Type"),
624 "MSysObjects should have 'Type' column, found: {col_names:?}"
625 );
626
627 assert!(
628 !tdef.data_pages.is_empty(),
629 "MSysObjects should have at least one data page"
630 );
631 }
632
633 #[test]
634 fn jet3_msysobjects() {
635 let path = skip_if_missing!("V1997/testV1997.mdb");
636 let mut reader = PageReader::open(&path).unwrap();
637 let tdef = read_table_def(&mut reader, "MSysObjects", CATALOG_PAGE).unwrap();
638 assert_msysobjects(&tdef);
639 }
640
641 #[test]
642 fn jet4_msysobjects() {
643 let path = skip_if_missing!("V2003/testV2003.mdb");
644 let mut reader = PageReader::open(&path).unwrap();
645 let tdef = read_table_def(&mut reader, "MSysObjects", CATALOG_PAGE).unwrap();
646 assert_msysobjects(&tdef);
647 }
648
649 #[test]
650 fn ace12_msysobjects() {
651 let path = skip_if_missing!("V2007/testV2007.accdb");
652 let mut reader = PageReader::open(&path).unwrap();
653 let tdef = read_table_def(&mut reader, "MSysObjects", CATALOG_PAGE).unwrap();
654 assert_msysobjects(&tdef);
655 }
656
657 #[test]
658 fn ace14_msysobjects() {
659 let path = skip_if_missing!("V2010/testV2010.accdb");
660 let mut reader = PageReader::open(&path).unwrap();
661 let tdef = read_table_def(&mut reader, "MSysObjects", CATALOG_PAGE).unwrap();
662 assert_msysobjects(&tdef);
663 }
664
665 #[test]
666 fn columns_sorted_by_col_num() {
667 let path = skip_if_missing!("V2003/testV2003.mdb");
668 let mut reader = PageReader::open(&path).unwrap();
669 let tdef = read_table_def(&mut reader, "MSysObjects", CATALOG_PAGE).unwrap();
670 for w in tdef.columns.windows(2) {
671 assert!(
672 w[0].col_num <= w[1].col_num,
673 "columns should be sorted by col_num"
674 );
675 }
676 }
677
678 #[test]
679 fn invalid_page_type_error() {
680 let path = skip_if_missing!("V2003/testV2003.mdb");
681 let mut reader = PageReader::open(&path).unwrap();
682 let result = read_table_def(&mut reader, "bad", 1);
684 assert!(result.is_err());
685 }
686
687 fn find_table_page(reader: &mut PageReader, table_name: &str) -> Option<u32> {
691 let catalog = crate::catalog::read_catalog(reader).ok()?;
692 catalog
693 .iter()
694 .find(|e| e.name == table_name)
695 .map(|e| e.table_page)
696 }
697
698 fn assert_user_table_indexes(path: &std::path::Path, table_name: &str) -> TableDef {
699 let mut reader = PageReader::open(path).unwrap();
700 let page = find_table_page(&mut reader, table_name)
701 .unwrap_or_else(|| panic!("table '{table_name}' not found in catalog"));
702 read_table_def(&mut reader, table_name, page).unwrap()
703 }
704
705 #[test]
706 fn jet4_index_count() {
707 let path = skip_if_missing!("V2003/testV2003.mdb");
708 let tdef = assert_user_table_indexes(&path, "Table1");
709 assert!(
710 !tdef.indexes.is_empty(),
711 "Table1 should have at least one index"
712 );
713 }
714
715 #[test]
716 fn jet3_index_count() {
717 let path = skip_if_missing!("V1997/testV1997.mdb");
718 let tdef = assert_user_table_indexes(&path, "Table1");
719 assert!(
720 !tdef.indexes.is_empty(),
721 "Jet3 Table1 should have at least one index"
722 );
723 }
724
725 #[test]
726 fn ace12_index_count() {
727 let path = skip_if_missing!("V2007/testV2007.accdb");
728 let tdef = assert_user_table_indexes(&path, "Table1");
729 assert!(
730 !tdef.indexes.is_empty(),
731 "ACE12 Table1 should have at least one index"
732 );
733 }
734
735 #[test]
736 fn ace14_index_count() {
737 let path = skip_if_missing!("V2010/testV2010.accdb");
738 let tdef = assert_user_table_indexes(&path, "Table1");
739 assert!(
740 !tdef.indexes.is_empty(),
741 "ACE14 Table1 should have at least one index"
742 );
743 }
744
745 #[test]
746 fn jet4_primary_key() {
747 let path = skip_if_missing!("V2003/testV2003.mdb");
748 let tdef = assert_user_table_indexes(&path, "Table1");
749
750 let pk = tdef
751 .indexes
752 .iter()
753 .find(|idx| idx.name == "PrimaryKey")
754 .expect("Table1 should have a PrimaryKey index");
755
756 assert_ne!(
757 pk.flags & crate::format::index_flags::UNIQUE,
758 0,
759 "PrimaryKey should have UNIQUE flag"
760 );
761 assert_ne!(
762 pk.flags & crate::format::index_flags::REQUIRED,
763 0,
764 "PrimaryKey should have REQUIRED flag"
765 );
766 assert!(
767 !pk.columns.is_empty(),
768 "PrimaryKey should have at least one column"
769 );
770 }
771
772 #[test]
773 fn jet4_index_columns() {
774 let path = skip_if_missing!("V2003/testV2003.mdb");
775 let tdef = assert_user_table_indexes(&path, "Table1");
776
777 for idx in &tdef.indexes {
778 if idx.index_type != crate::format::index_type::FOREIGN_KEY {
779 assert!(
780 !idx.columns.is_empty(),
781 "non-FK index '{}' should have columns",
782 idx.name
783 );
784 for col in &idx.columns {
785 assert!(
786 (col.col_num as usize) < tdef.columns.len() + 256,
787 "index column number should be reasonable"
788 );
789 }
790 }
791 }
792 }
793
794 #[test]
795 fn index_fk_type() {
796 let path = skip_if_missing!("V2003/indexTestV2003.mdb");
798 let tdef = assert_user_table_indexes(&path, "Table1");
799
800 let fk_indexes: Vec<&IndexDef> = tdef
801 .indexes
802 .iter()
803 .filter(|idx| idx.index_type == crate::format::index_type::FOREIGN_KEY)
804 .collect();
805
806 assert!(
807 !fk_indexes.is_empty(),
808 "indexTest Table1 should have FK indexes"
809 );
810
811 for fk in &fk_indexes {
812 assert!(
813 fk.foreign_key.is_some(),
814 "FK index '{}' should have foreign_key info",
815 fk.name
816 );
817 assert!(
818 fk.columns.is_empty(),
819 "FK index '{}' should have no columns",
820 fk.name
821 );
822 }
823 }
824
825 #[test]
826 fn jet3_index_fk_type() {
827 let path = skip_if_missing!("V1997/indexTestV1997.mdb");
828 let tdef = assert_user_table_indexes(&path, "Table1");
829
830 let fk_indexes: Vec<&IndexDef> = tdef
831 .indexes
832 .iter()
833 .filter(|idx| idx.index_type == crate::format::index_type::FOREIGN_KEY)
834 .collect();
835
836 assert!(
837 !fk_indexes.is_empty(),
838 "Jet3 indexTest Table1 should have FK indexes"
839 );
840 for fk in &fk_indexes {
841 assert!(fk.foreign_key.is_some());
842 }
843 }
844
845 #[test]
848 fn is_replication_true() {
849 let col = ColumnDef {
850 name: "s_GUID".to_string(),
851 col_type: ColumnType::Guid,
852 col_num: 1,
853 var_col_num: 0,
854 fixed_offset: 0,
855 col_size: 16,
856 flags: column_flags::REPLICATION | column_flags::NULLABLE,
857 is_fixed: false,
858 precision: 0,
859 scale: 0,
860 };
861 assert!(is_replication_column(&col));
862 }
863
864 #[test]
865 fn is_replication_false() {
866 let col = ColumnDef {
867 name: "ID".to_string(),
868 col_type: ColumnType::Long,
869 col_num: 1,
870 var_col_num: 0,
871 fixed_offset: 0,
872 col_size: 4,
873 flags: column_flags::FIXED,
874 is_fixed: true,
875 precision: 0,
876 scale: 0,
877 };
878 assert!(!is_replication_column(&col));
879 }
880
881 #[test]
882 fn index_names_are_nonempty() {
883 let path = skip_if_missing!("V2003/testV2003.mdb");
884 let tdef = assert_user_table_indexes(&path, "Table1");
885
886 for idx in &tdef.indexes {
887 assert!(!idx.name.is_empty(), "index name should not be empty");
888 }
889 }
890
891 #[test]
894 fn read_names_jet3_latin1() {
895 let buf = [3, b'F', b'o', b'o', 3, b'B', b'a', b'r'];
897 let mut cursor = TdefCursor::new(&buf, 0);
898 let names = read_names(&mut cursor, 2, true).unwrap();
899 assert_eq!(names, vec!["Foo", "Bar"]);
900 assert_eq!(cursor.position(), 8);
901 }
902
903 #[test]
904 fn read_names_jet4_utf16le() {
905 let buf = [
908 4, 0, b'A', 0, b'b', 0, 2, 0, b'X', 0, ];
913 let mut cursor = TdefCursor::new(&buf, 0);
914 let names = read_names(&mut cursor, 2, false).unwrap();
915 assert_eq!(names, vec!["Ab", "X"]);
916 assert_eq!(cursor.position(), 10);
917 }
918
919 #[test]
920 fn read_names_boundary_error() {
921 let buf = [3, b'A', b'B'];
923 let mut cursor = TdefCursor::new(&buf, 0);
924 let result = read_names(&mut cursor, 1, true);
925 assert!(result.is_err());
926 }
927
928 #[test]
929 fn read_names_empty_count() {
930 let buf = [];
931 let mut cursor = TdefCursor::new(&buf, 0);
932 let names = read_names(&mut cursor, 0, true).unwrap();
933 assert!(names.is_empty());
934 assert_eq!(cursor.position(), 0);
935 }
936
937 #[test]
940 fn parse_column_entries_jet3() {
941 use crate::format::JET3;
942 let mut entry = vec![0u8; JET3.tdef_column_entry_span];
944 entry[0] = ColumnType::Long.to_byte(); entry[JET3.coldef_number_pos] = 5; entry[JET3.coldef_flags_pos] = column_flags::FIXED;
947 entry[JET3.coldef_length_pos] = 4;
948 entry[JET3.coldef_length_pos + 1] = 0;
949
950 let mut cursor = TdefCursor::new(&entry, 0);
951 let cols =
952 parse_column_entries(&mut cursor, JET3.tdef_column_entry_span, 1, true, &JET3).unwrap();
953 assert_eq!(cols.len(), 1);
954 assert_eq!(cols[0].col_type, ColumnType::Long);
955 assert_eq!(cols[0].col_num, 5);
956 assert!(cols[0].is_fixed);
957 assert_eq!(cols[0].col_size, 4);
958 }
959
960 #[test]
961 fn parse_column_entries_jet4() {
962 use crate::format::JET4;
963 let mut entry = vec![0u8; JET4.tdef_column_entry_span];
965 entry[0] = ColumnType::Text.to_byte();
966 entry[JET4.coldef_number_pos] = 3;
968 entry[JET4.coldef_number_pos + 1] = 0;
969 entry[JET4.coldef_flags_pos] = column_flags::NULLABLE;
970 entry[JET4.coldef_length_pos] = 0xFF;
971 entry[JET4.coldef_length_pos + 1] = 0;
972
973 let mut cursor = TdefCursor::new(&entry, 0);
974 let cols = parse_column_entries(&mut cursor, JET4.tdef_column_entry_span, 1, false, &JET4)
975 .unwrap();
976 assert_eq!(cols.len(), 1);
977 assert_eq!(cols[0].col_type, ColumnType::Text);
978 assert_eq!(cols[0].col_num, 3);
979 assert!(!cols[0].is_fixed);
980 assert_eq!(cols[0].col_size, 255);
981 }
982
983 #[test]
986 fn cursor_read_u8() {
987 let buf = [0xAB, 0xCD];
988 let mut cursor = TdefCursor::new(&buf, 0);
989 assert_eq!(cursor.read_u8().unwrap(), 0xAB);
990 assert_eq!(cursor.position(), 1);
991 assert_eq!(cursor.read_u8().unwrap(), 0xCD);
992 assert_eq!(cursor.position(), 2);
993 }
994
995 #[test]
996 fn cursor_read_u16_le() {
997 let buf = [0x34, 0x12, 0x78, 0x56];
998 let mut cursor = TdefCursor::new(&buf, 0);
999 assert_eq!(cursor.read_u16_le().unwrap(), 0x1234);
1000 assert_eq!(cursor.position(), 2);
1001 assert_eq!(cursor.read_u16_le().unwrap(), 0x5678);
1002 assert_eq!(cursor.position(), 4);
1003 }
1004
1005 #[test]
1006 fn cursor_read_u32_le() {
1007 let buf = [0x78, 0x56, 0x34, 0x12];
1008 let mut cursor = TdefCursor::new(&buf, 0);
1009 assert_eq!(cursor.read_u32_le().unwrap(), 0x12345678);
1010 assert_eq!(cursor.position(), 4);
1011 }
1012
1013 #[test]
1014 fn cursor_read_bytes() {
1015 let buf = [1, 2, 3, 4, 5];
1016 let mut cursor = TdefCursor::new(&buf, 1);
1017 let bytes = cursor.read_bytes(3).unwrap();
1018 assert_eq!(bytes, &[2, 3, 4]);
1019 assert_eq!(cursor.position(), 4);
1020 }
1021
1022 #[test]
1023 fn cursor_skip() {
1024 let buf = [0u8; 10];
1025 let mut cursor = TdefCursor::new(&buf, 0);
1026 cursor.skip(5).unwrap();
1027 assert_eq!(cursor.position(), 5);
1028 cursor.skip(5).unwrap();
1029 assert_eq!(cursor.position(), 10);
1030 }
1031
1032 #[test]
1033 fn cursor_out_of_bounds() {
1034 let buf = [0xAB];
1035 let mut cursor = TdefCursor::new(&buf, 0);
1036 assert!(cursor.read_u16_le().is_err());
1037 assert!(cursor.read_u32_le().is_err());
1038 cursor.read_u8().unwrap(); assert!(cursor.read_u8().is_err());
1040 assert!(cursor.read_bytes(1).is_err());
1041 assert!(cursor.skip(1).is_err());
1042 }
1043
1044 #[test]
1045 fn cursor_u8_at() {
1046 let buf = [0x10, 0x20, 0x30];
1047 let cursor = TdefCursor::new(&buf, 0);
1048 assert_eq!(cursor.u8_at(1).unwrap(), 0x20);
1049 assert_eq!(cursor.position(), 0); assert!(cursor.u8_at(3).is_err());
1051 }
1052
1053 #[test]
1054 fn cursor_u16_le_at() {
1055 let buf = [0x00, 0x34, 0x12];
1056 let cursor = TdefCursor::new(&buf, 0);
1057 assert_eq!(cursor.u16_le_at(1).unwrap(), 0x1234);
1058 assert_eq!(cursor.position(), 0); assert!(cursor.u16_le_at(2).is_err());
1060 }
1061
1062 #[test]
1067 fn build_index_defs_normal_index() {
1068 let logical = vec![LogicalIndex {
1069 index_num: 1,
1070 index_col_entry: 0,
1071 fk_index_type: 0,
1072 fk_index_number: 0,
1073 fk_table_page: 0,
1074 update_action: 0,
1075 delete_action: 0,
1076 index_type: crate::format::index_type::NORMAL,
1077 }];
1078 let col = IndexColumn {
1079 col_num: 3,
1080 order: IndexColumnOrder::Ascending,
1081 };
1082 let physical: Vec<PhysicalIndexEntry> = vec![(vec![col], 0x01, 100)];
1083 let names = vec!["PK_Id".to_string()];
1084
1085 let result = build_index_defs(&logical, &physical, names);
1086 assert_eq!(result.len(), 1);
1087 assert_eq!(result[0].name, "PK_Id");
1088 assert_eq!(result[0].index_num, 1);
1089 assert_eq!(result[0].columns.len(), 1);
1090 assert_eq!(result[0].columns[0].col_num, 3);
1091 assert_eq!(result[0].flags, 0x01);
1092 assert_eq!(result[0].first_data_page, 100);
1093 assert!(result[0].foreign_key.is_none());
1094 }
1095
1096 #[test]
1097 fn build_index_defs_foreign_key() {
1098 let logical = vec![LogicalIndex {
1099 index_num: 2,
1100 index_col_entry: 0,
1101 fk_index_type: 1,
1102 fk_index_number: 5,
1103 fk_table_page: 42,
1104 update_action: 1,
1105 delete_action: 2,
1106 index_type: crate::format::index_type::FOREIGN_KEY,
1107 }];
1108 let physical: Vec<PhysicalIndexEntry> = vec![];
1109 let names = vec!["FK_Ref".to_string()];
1110
1111 let result = build_index_defs(&logical, &physical, names);
1112 assert_eq!(result.len(), 1);
1113 assert_eq!(result[0].name, "FK_Ref");
1114 assert!(result[0].columns.is_empty());
1115 let fk = result[0].foreign_key.as_ref().unwrap();
1116 assert_eq!(fk.fk_index_type, 1);
1117 assert_eq!(fk.fk_index_number, 5);
1118 assert_eq!(fk.fk_table_page, 42);
1119 assert_eq!(fk.update_action, 1);
1120 assert_eq!(fk.delete_action, 2);
1121 }
1122
1123 #[test]
1124 fn build_index_defs_out_of_range_warning() {
1125 let logical = vec![LogicalIndex {
1127 index_num: 3,
1128 index_col_entry: 99, fk_index_type: 0,
1130 fk_index_number: 0,
1131 fk_table_page: 0,
1132 update_action: 0,
1133 delete_action: 0,
1134 index_type: crate::format::index_type::NORMAL,
1135 }];
1136 let physical: Vec<PhysicalIndexEntry> = vec![]; let names = vec!["BadIdx".to_string()];
1138
1139 let result = build_index_defs(&logical, &physical, names);
1140 assert_eq!(result.len(), 1);
1141 assert_eq!(result[0].name, "BadIdx");
1142 assert!(result[0].columns.is_empty());
1143 assert_eq!(result[0].flags, 0);
1144 assert_eq!(result[0].first_data_page, 0);
1145 assert!(result[0].foreign_key.is_none());
1146 }
1147
1148 #[test]
1149 fn build_index_defs_name_missing_uses_default() {
1150 let logical = vec![
1152 LogicalIndex {
1153 index_num: 0,
1154 index_col_entry: 0,
1155 fk_index_type: 0,
1156 fk_index_number: 0,
1157 fk_table_page: 0,
1158 update_action: 0,
1159 delete_action: 0,
1160 index_type: crate::format::index_type::NORMAL,
1161 },
1162 LogicalIndex {
1163 index_num: 1,
1164 index_col_entry: 0,
1165 fk_index_type: 0,
1166 fk_index_number: 0,
1167 fk_table_page: 0,
1168 update_action: 0,
1169 delete_action: 0,
1170 index_type: crate::format::index_type::NORMAL,
1171 },
1172 ];
1173 let col = IndexColumn {
1174 col_num: 1,
1175 order: IndexColumnOrder::Ascending,
1176 };
1177 let physical: Vec<PhysicalIndexEntry> = vec![(vec![col], 0, 0)];
1178 let names = vec!["OnlyOne".to_string()]; let result = build_index_defs(&logical, &physical, names);
1181 assert_eq!(result.len(), 2);
1182 assert_eq!(result[0].name, "OnlyOne");
1183 assert_eq!(result[1].name, ""); }
1185}