8000 Tell those who read an Access 97 form that jetdb does not read it, not that it is missing by dominion525 · Pull Request #176 · dominion525/jetdb · GitHub
[go: up one dir, main page]

Skip to content
8000
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions crates/jetdb-cli/tests/cli_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,32 @@ fn form_props_shared_name_needs_type() {
assert!(stdout.starts_with(header), "{object_type}: {stdout}");
}
}

// ---------------------------------------------------------------------------
// Access 97: forms and reports are not read
// ---------------------------------------------------------------------------

#[test]
fn form_access97_not_read() {
// nwind.mdb has a form named Customers, in the format of Access 97.
let path = skip_if_missing!("V1997/nwind.mdb");
let output = jetdb_bin()
.args(["form", "list", path.to_str().unwrap()])
.output()
.expect("failed to run jetdb");
assert!(output.status.success());
assert!(output.stdout.is_empty(), "should list nothing");

for command in ["props", "controls", "dump"] {
let output = jetdb_bin()
.args(["form", command, path.to_str().unwrap(), "Customers"])
.output()
.expect("failed to run jetdb");
assert!(!output.status.success(), "{command}");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("the forms and reports of Access 97 are not read"),
"{command}: {stderr}"
);
}
}
4 changes: 3 additions & 1 deletion crates/jetdb-wasm/src/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ export interface Rows {
* that name.
* - `INVALID_FILE`: not an Access database, or a broken one; the message
* says what is wrong.
* - `NOT_SUPPORTED`: the database holds what jetdb does not read, such as
* the forms and reports of Access 97.
* - `INVALID_ARGUMENT`: a method was called with an argument or option that
* is not valid, such as a value of the wrong type or one outside its
* list, or with a name that objects of different types share and no
Expand All @@ -499,7 +501,7 @@ export type ErrorCode =
| "PASSWORD_REQUIRED" | "INVALID_PASSWORD" | "UNSUPPORTED_ENCRYPTION"
| "TABLE_NOT_FOUND" | "QUERY_NOT_FOUND" | "MODULE_NOT_FOUND"
| "FORM_NOT_FOUND" | "MACRO_NOT_FOUND" | "OBJECT_NOT_FOUND"
| "INVALID_FILE" | "INVALID_ARGUMENT" | "IO";
| "INVALID_FILE" | "NOT_SUPPORTED" | "INVALID_ARGUMENT" | "IO";

/** The error that the methods of `Database` throw. */
export interface JetdbError extends Error {
Expand Down
2 changes: 2 additions & 0 deletions crates/jetdb-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,7 @@ pub fn error_code(error: &FileError) -> &'static str {
| FileError::InvalidFormData { .. }
| FileError::InvalidMacroData { .. }
| FileError::IncompleteQuery { .. } => "INVALID_FILE",
FileError::Jet3FormsNotRead => "NOT_SUPPORTED",
FileError::Io(_) => "IO",
_ => "INVALID_FILE",
}
Expand Down Expand Up @@ -2971,6 +2972,7 @@ mod tests {
),
(FileError::InvalidTableDef { reason: "x" }, "INVALID_FILE"),
(FileError::IncompleteQuery { name: name() }, "INVALID_FILE"),
(FileError::Jet3FormsNotRead, "NOT_SUPPORTED"),
(FileError::Io(std::io::Error::other("x")), "IO"),
];
for (error, code) in codes {
Expand Down
6 changes: 6 additions & 0 deletions crates/jetdb/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub enum FileError {
FormNotFound {
name: String,
},
/// The database is an Access 97 (Jet3) one, which keeps its forms and
/// reports in another format that jetdb does not read.
Jet3FormsNotRead,
MacroNotFound {
name: String,
},
Expand Down Expand Up @@ -108,6 +111,9 @@ impl fmt::Display for FileError {
Self::QueryNotFound { name } => write!(f, "query not found: {name}"),
Self::ModuleNotFound { name } => write!(f, "VBA module not found: {name}"),
Self::FormNotFound { name } => write!(f, "form/report not found: {name}"),
Self::Jet3FormsNotRead => {
write!(f, "the forms and reports of Access 97 are not read")
}
Self::MacroNotFound { name } => write!(f, "macro not found: {name}"),
Self::IncompleteQuery { name } => write!(
f,
Expand Down
55 changes: 35 additions & 20 deletions crates/jetdb/src/form.rs
< 8BD0 td class="blob-num blob-num-deletion empty-cell">
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ pub(crate) fn stream_in(
object_type: Option<FormObjectType>,
stream_kind: StreamKind,
) -> Result<FormStream, FileError> {
let (object_type, stream_data) = find_stream(&storage.entries, name, object_type, stream_kind)?;
let (object_type, stream_data) = find_stream(storage, name, object_type, stream_kind)?;

Ok(FormStream {
name: name.to_string(),
Expand Down Expand Up @@ -258,9 +258,8 @@ pub fn read_form_type_info_in(
name: &str,
object_type: Option<FormObjectType>,
) -> Result<FormTypeInfo, FileError> {
let (object_type, stream_data) =
find_stream(&storage.entries, name, object_type, StreamKind::TypeInfo)?;
let names = blob_names(&storage 8000 .entries, name, object_type);
let (object_type, stream_data) = find_stream(storage, name, object_type, StreamKind::TypeInfo)?;
let names = blob_names(storage, name, object_type);
let (controls, _) = type_info_named(&stream_data, storage.code_page, &names)?;

Ok(FormTypeInfo {
Expand Down Expand Up @@ -697,8 +696,7 @@ pub fn read_form_properties_in(
name: &str,
object_type: Option<FormObjectType>,
) -> Result<FormProperties, FileError> {
let entries = &storage.entries;
let (object_type, blob_data) = find_stream(entries, name, object_type, StreamKind::Blob)?;
let (object_type, blob_data) = find_stream(storage, name, object_type, StreamKind::Blob)?;

// The Access 2000 format keeps the type of each control in the Blob.
if blob_data.first() == Some(&crate::form2000::VERSION) {
Expand Down Expand Up @@ -736,12 +734,7 @@ fn type_info_controls(
object_type: FormObjectType,
names: &[String],
) -> Vec<ControlInfo> {
match find_stream(
&storage.entries,
name,
Some(object_type),
StreamKind::TypeInfo,
) {
match find_stream(storage, name, Some(object_type), StreamKind::TypeInfo) {
Ok((_, ti_data)) => type_info_named(&ti_data, storage.code_page, names)
.map(|(controls, _)| controls)
.unwrap_or_default(),
Expand Down Expand Up @@ -785,12 +778,8 @@ fn name_in_blob(props: &[BlobProperty]) -> Option<String> {

/// The names of the controls of the Blob of the object `name` of
/// `object_type`, empty when it has none that can be read.
fn blob_names(
entries: &[storage::StorageEntry],
name: &str,
object_type: FormObjectType,
) -> Vec<String> {
let Ok((_, blob)) = find_stream(entries, name, Some(object_type), StreamKind::Blob) else {
fn blob_names(storage: &AccessStorage, name: &str, object_type: FormObjectType) -> Vec<String> {
let Ok((_, blob)) = find_stream(storage, name, Some(object_type), StreamKind::Blob) else {
return Vec::new();
};
if blob.first() == Some(&crate::form2000::VERSION) {
Expand Down Expand Up @@ -858,13 +847,18 @@ fn type_info_by_name<'a>(controls: &'a [ControlInfo], name: &str) -> Option<&'a
///
/// Searches the Forms folder and then the Reports folder, or only the one
/// of `object_type` when it is given. Returns the object type and the
/// stream binary data.
/// stream binary data. Fails in an Access 97 database, whose forms and
/// reports jetdb does not read.
fn find_stream(
entries: &[storage::StorageEntry],
storage: &AccessStorage,
name: &str,
object_type: Option<FormObjectType>,
stream_kind: StreamKind,
) -> Result<(FormObjectType, Vec<u8>), FileError> {
if storage.jet3 {
return Err(FileError::Jet3FormsNotRead);
}
let entries = &storage.entries;
// Try Forms first, then Reports
for (folder_name, obj_type) in FORM_FOLDERS {
if object_type.is_some_and(|t| t != obj_type) {
Expand Down Expand Up @@ -1524,6 +1518,27 @@ mod tests {
assert!(forms.is_empty(), "expected no forms in plain test database");
}

#[test]
fn access97_forms_not_read() {
// nwind.mdb has a form named Customers, in the format of Access 97:
// none is listed, and reading one fails as not read, not as not found.
let path = skip_if_missing!("V1997/nwind.mdb");
let mut reader = PageReader::open(&path).unwrap();
assert!(list_forms(&mut reader).unwrap().is_empty());
assert!(matches!(
read_form_stream(&mut reader, "Customers", StreamKind::Blob),
Err(FileError::Jet3FormsNotRead)
));
assert!(matches!(
read_form_type_info(&mut reader, "Customers"),
Err(FileError::Jet3FormsNotRead)
));
assert!(matches!(
read_form_properties_of_type(&mut reader, "Customers", FormObjectType::Report),
Err(FileError::Jet3FormsNotRead)
));
}

// -- Blob parser unit tests (synthetic binary) ----------------------------

/// Build a minimal blob with 8-byte header + 6-byte preamble + entries.
Expand Down
4 changes: 4 additions & 0 deletions crates/jetdb/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ pub struct AccessStorage {
/// The code page in the header of the database, in which jetdb reads the
/// names in the TypeInfo of forms and reports.
pub(crate) code_page: u16,
/// Whether the database is an Access 97 (Jet3) one, whose forms and
/// reports jetdb does not read.
pub(crate) jet3: bool,
}

/// Read the storage of forms, reports, macros and VBA (see [`AccessStorage`]).
pub fn read_access_storage(reader: &mut PageReader) -> Result<AccessStorage, FileError> {
Ok(AccessStorage {
entries: read_storage_entries(reader)?,
code_page: reader.header().code_page,
jet3: reader.header().version.is_jet3(),
})
}

Expand Down
2 changes: 2 additions & 0 deletions docs/cli.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,8 @@ End Function

### form — フォーム/レポートの管理

Access 97 (Jet3) のデータベースのフォームとレポートは別の形式で保存されており、jetdb は読まない。`form list` は何も出力せず、`form dump`・`form controls`・`form props` は読まないことを伝えるエラーで失敗する。

#### form list — フォーム/レポート名の一覧表示

```
Expand Down
2 changes: 2 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ End Function

### form — Manage forms and reports

jetdb does not read the forms and reports of an Access 97 (Jet3) database, which are stored in another format: `form list` produces no output, and `form dump`, `form controls` and `form props` fail with an error saying that they are not read.

#### form list — List form and report names

```
Expand Down
3 changes: 2 additions & 1 deletion docs/wasm.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ do {
| `UNSUPPORTED_ENCRYPTION` | jetdb が読めない方式で暗号化されている |
| `TABLE_NOT_FOUND`、`QUERY_NOT_FOUND`、`MODULE_NOT_FOUND`、`FORM_NOT_FOUND`、`MACRO_NOT_FOUND`、`OBJECT_NOT_FOUND` | その名前のオブジェクトが無い |
| `INVALID_FILE` | Access のデータベースでないか、壊れている。何が悪いかはメッセージにある |
| `NOT_SUPPORTED` | Access 97 のフォームとレポートのように、jetdb が読まないものをデータベースが持っている |
| `INVALID_ARGUMENT` | メソッドに正しくない引数やオプションが渡された(型が違う、決まった値のどれでもない、など)。または、種類の違うオブジェクトが同じ名前で、どれかを選ぶ `type` のオプションが無い |
| `IO` | バイト列の読み取りに失敗した |

Expand All @@ -182,4 +183,4 @@ try {

- 読み取り専用です(Access のファイルには書き込みません)。
- ファイル全体をメモリに持ちます。
- Access 97(Jet3)のファイルでは、フォーム・レポートと VBA を読みません。`forms()` と `modules()` は何も返しません。
- Access 97(Jet3)のファイルでは、フォーム・レポートと VBA を読みません。`forms()` と `modules()` は何も返さず、名前を指定してフォームやレポートを読むと `NOT_SUPPORTED` を投げます。
3 changes: 2 additions & 1 deletion docs/wasm.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ A method that fails throws an `Error` whose `name` is `JetdbError` and whose `co
| `UNSUPPORTED_ENCRYPTION` | The file is encrypted in a way jetdb cannot read |
| `TABLE_NOT_FOUND`, `QUERY_NOT_FOUND`, `MODULE_NOT_FOUND`, `FORM_NOT_FOUND`, `MACRO_NOT_FOUND`, `OBJECT_NOT_FOUND` | No object of that name |
| `INVALID_FILE` | Not an Access database, or a broken one; the message says what is wrong |
| `NOT_SUPPORTED` | The database holds what jetdb does not read, such as the forms and reports of Access 97 |
| `INVALID_ARGUMENT` | A method was called with an argument or option that is not valid, such as a value of the wrong type or one outside its list, or with a name that objects of different types share and no `type` option to choose one |
| `IO` | Reading the bytes failed |

Expand All @@ -182,4 +183,4 @@ try {

- Read-only: jetdb does not write to Access files.
- The whole file is held in memory.
- In an Access 97 (Jet3) file, jetdb does not read the forms, reports and VBA: `forms()` and `modules()` give none.
- In an Access 97 (Jet3) file, jetdb does not read the forms, reports and VBA: `forms()` and `modules()` give none, and reading a form or report by its name throws `NOT_SUPPORTED`.
Loading
0