Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: improve docs & bump version #224

Merged
merged 5 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## [0.21.0] - 2024-06-28

**Breaking changes**:
* runner: `RecordOutput` is now returned by `Runner::run` (or `Runner::run_async`). This allows users to access the output of each record, or check whether the record is skipped.
* runner(substitution): add a special variable `__NOW__` which will be replaced with the current Unix timestamp in nanoseconds.
Expand Down
143 changes: 103 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,16 @@ This repository provides two crates:

## Use the library

To add the dependency to your project:

```sh
cargo add sqllogictest
```

Implement `DB` trait for your database structure:

```rust
struct Database {...}

impl sqllogictest::DB for Database {
type Error = ...;
type ColumnType = ...;
fn run(&mut self, sql: &str) -> Result<sqllogictest::DBOutput<Self::ColumnType>, Self::Error> {
...
}
}
```

Then create a `Runner` on your database instance, and run the tests:

```rust
let db = Database {...};
let mut tester = sqllogictest::Runner::new(db);
tester.run_file("script.slt").unwrap();
```

You can also parse the script and execute the records separately:

```rust
let records = sqllogictest::parse_file("script.slt").unwrap();
for record in records {
tester.run(record).unwrap();
}
```
Refer to the [rustdoc](https://docs.rs/sqllogictest/latest/sqllogictest/).

## Use the CLI tool

![demo](./docs/demo.gif)
The CLI tool supports many useful features:
- Colorful diff output
- Automatically update test files according to the actual output
- JUnit format test result report
- Parallel execution isolated with different databases
- ...

To install the binary:

Expand All @@ -64,11 +34,12 @@ cargo install sqllogictest-bin
You can use it as follows:

```sh
# run scripts in `test` directory against postgres with default connection settings
sqllogictest './test/**/*.slt'
# run the tests, and update the test files with the actual output!
sqllogictest './test/**/*.slt' --override
```

This command will run scripts in `test` directory against postgres with default connection settings.

You can find more options in `sqllogictest --help` .

> **Note**
Expand Down Expand Up @@ -102,15 +73,107 @@ SELECT * FROM foo;
4 5
```

### Run a statement that should fail
### Extension: Run a query/statement that should fail with the expacted error message

The syntax:
- Do not check the error message: `[statement|query] error`
- Single line error message (regexp match): `[statement|query] error <regex>` and ` error <regex>`
xxchan marked this conversation as resolved.
Show resolved Hide resolved
- Multiline error message (exact match): Use `----`.

```text
# Ensure that the statement errors and that the error
# message contains 'Multiple object drop not supported'
statement error Multiple object drop not supported
DROP VIEW foo, bar;

# The output error message must be the exact match of the expected one to pass the test,
# except for the leading and trailing whitespaces.
# Empty lines (not consecutive) are allowed in the expected error message. As a result, the message must end with 2 consecutive empty lines.
query error
SELECT 1/0;
----
db error: ERROR: Failed to execute query

Caused by these errors:
1: Failed to evaluate expression: 1/0
2: Division by zero


# The next record begins here after 2 blank lines.
```

### Extension: Run external shell commands

This is useful for manipulating some external resources during the test.

```text
system ok
exit 0

# The runner will check the exit code of the command, and this will fail.
system ok
exit 1

# Check the output of the command. Same as `error`, empty lines (not consecutive) are allowed, and 2 consecutive empty lines ends the result.
system ok
echo "Hello\n\nWorld"
----
Hello

World


# The next record begins here after 2 blank lines.

# Environment variables are supported.
system ok
echo $USER
----
xxchan
```

### Extension: Environment variable substituion in query and statement

It needs to be enabled by adding `control substitution on` to the test file.

```
control substitution on

# see https://docs.rs/subst/latest/subst/ for all features
query TTTT
SELECT
'$foo' -- short
, '${foo}' -- long
, '${bar:default}' -- default value
, '${bar:$foo-default}' -- recursive default value
FROM baz;
----
...
```

Besides, there're some special variables supported:
- `$__TEST_DIR__`: the path to a temporary directory specific to the current test case.
This can be helpful if you need to manipulate some external resources during the test.
- `$__NOW__`: the current Unix timestamp in nanoseconds.

```
control substitution on

statement ok
COPY (SELECT * FROM foo) TO '$__TEST_DIR__/foo.txt';

system ok
echo "foo" > "$__TEST_DIR__/foo.txt"
```

> [!NOTE]
>
> When substitution is on, special characters need to be excaped, e.g., `\$` and `\\`.
>
> `system` commands don't support the advanced substitution features of the [subst](https://docs.rs/subst/latest/subst/) crate,
> and excaping is also not needed.
> Environment variables are supported by the shell, and special variables are still supported by plain string substitution.

## Used by

- [RisingLight](https://github.com/risinglightdb/risinglight): An OLAP database system for educational purpose
Expand Down
Binary file removed docs/demo.gif
Binary file not shown.
89 changes: 0 additions & 89 deletions docs/demo.tape

This file was deleted.

22 changes: 15 additions & 7 deletions sqllogictest/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
//! [Sqllogictest][Sqllogictest] parser and runner.
//!
//! This crate supports multiple extensions beyond the original sqllogictest format.
//! See the [README](https://github.com/risinglightdb/sqllogictest-rs#slt-test-file-format-cookbook) for more information.
//!
//! [Sqllogictest]: https://www.sqlite.org/sqllogictest/doc/trunk/about.wiki
//!
//! # Usage
//!
//! For how to use the CLI tool backed by this library, see the [README](https://github.com/risinglightdb/sqllogictest-rs#use-the-cli-tool).
//!
//! For using the crate as a lib, and implement your custom driver, see below.
//!
//! Implement [`DB`] trait for your database structure:
//!
//! ```ignore
//! struct Database {...}
//!
//! impl sqllogictest::DB for Database {
//! type Error = ...;
//! fn run(&self, sql: &str) -> Result<String, Self::Error> {
//! type ColumnType = ...;
//! fn run(&mut self, sql: &str) -> Result<sqllogictest::DBOutput<Self::ColumnType>, Self::Error> {
//! ...
//! }
//! }
//! ```
//!
//! Create a [`Runner`] on your database instance, and then run the script:
//! Then create a `Runner` on your database instance, and run the tests:
//!
//! ```ignore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we test it to keep it well maintained?

//! let mut tester = sqllogictest::Runner::new(Database::new());
//! let script = std::fs::read_to_string("script.slt").unwrap();
//! tester.run_script(&script);
//! let db = Database {...};
//! let mut tester = sqllogictest::Runner::new(db);
//! tester.run_file("script.slt").unwrap();
//! ```
//!
//! You can also parse the script and execute the records separately:
//!
//! ```ignore
//! let records = sqllogictest::parse(&script).unwrap();
//! let records = sqllogictest::parse_file("script.slt").unwrap();
//! for record in records {
//! tester.run(record);
//! tester.run(record).unwrap();
//! }
//! ```

Expand Down
Loading