Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
65fa316
Red list: v1 readiness review findings
jgarzik Oct 11, 2026
7ea554c
Parse layer: SQLite precedence, left-deep compounds, depth limits
jgarzik Oct 11, 2026
2bab3be
Value semantics: one comparison rule, SQLite real rendering, Neumaier…
jgarzik Oct 11, 2026
a53a05f
Names: qualifiers must name a FROM source; main schema only; outer-on…
jgarzik Oct 11, 2026
4604395
Result column names follow SQLite
jgarzik Oct 11, 2026
c0232c2
File IO: file-type rules, verbatim writeback, load errors, table specs
jgarzik Oct 11, 2026
60d1f98
Errors and output: one-line errors, safe diagnostics, -f and output f…
jgarzik Oct 11, 2026
77513a7
Statements: transactions, writeback edges, no silent ignores
jgarzik Oct 11, 2026
a9eb816
CLAUDE.md: describe the current writeback rules
jgarzik Oct 11, 2026
34fea24
Tests: gate Unix-only permission and symlink tests with cfg(unix)
jgarzik Oct 11, 2026
fcddca5
REPL: script mode, exit codes, history location, completion
jgarzik Oct 11, 2026
71625b9
Tests: pass on Windows; spawn the built binary; fix a stdin race
jgarzik Oct 11, 2026
b93fb91
Red test: load error line numbers are one too low in CRLF files
jgarzik Oct 11, 2026
3bca3ad
Functions: SQLite printf and date.c ports, hex literals, bitwise ops
jgarzik Oct 11, 2026
9819386
Load errors name the right line in CRLF files
jgarzik Oct 11, 2026
5e559d3
gitattributes: keep test fixtures byte-exact on every platform
jgarzik Oct 11, 2026
281254b
Remove dead code and intent notes; test compound WHERE logic
jgarzik Oct 11, 2026
505b586
Release profile and metadata for 0.9.0
jgarzik Oct 11, 2026
3317968
Docs: accuracy pass, memory use, CHANGELOG for 0.9.0
jgarzik Oct 11, 2026
9a38475
SQLite diff harnesses: like-for-like typing, adversarial corpora, fuz…
jgarzik Oct 11, 2026
da39af7
Red tests: ORDER BY / GROUP BY integer terms follow SQLite
jgarzik Oct 11, 2026
b49ed05
Tests: pass deep-expression SQL on stdin, not the command line
jgarzik Oct 11, 2026
c456a98
ORDER BY / GROUP BY integer terms follow SQLite
jgarzik Oct 11, 2026
4f17d34
Remove CHANGELOG.md; git history is the record
jgarzik Oct 11, 2026
abb09c5
Red tests: LOCATION delimiter follows the extension; declared column …
jgarzik Oct 11, 2026
9220131
LOCATION delimiter follows the extension; declared types kept as written
jgarzik Oct 11, 2026
e4b8960
Docs: match the binary after the documentation review
jgarzik Oct 11, 2026
6ff155d
Red tests: CREATE TABLE ... LOCATION over an existing file
jgarzik Oct 11, 2026
d6746e9
CREATE TABLE ... LOCATION loads an existing file under the declared c…
jgarzik Oct 11, 2026
25b0da2
Docs: CREATE TABLE ... LOCATION over an existing file; where columns …
jgarzik Oct 11, 2026
e6ed855
FileHandler holds no Database pointer; SqlExecutor owns both
jgarzik Oct 12, 2026
90c1b11
Local time through chrono; forbid unsafe code in sqawk
jgarzik Oct 12, 2026
17f979a
datetime: module doc names chrono as the local time source
jgarzik Oct 12, 2026
80872ae
error.rs test: skip the glibc-wording assertion under Miri
jgarzik Oct 12, 2026
5b9fd38
Red list: common table expressions (WITH [RECURSIVE])
jgarzik Oct 12, 2026
60dc20f
Common table expressions: WITH [RECURSIVE]
jgarzik Oct 12, 2026
ebd5ad1
Recursive CTEs: one run per generation for FIFO queues
jgarzik Oct 12, 2026
1c473f3
Docs and differential corpus: common table expressions
jgarzik Oct 12, 2026
860f996
tsq: portable seeded RNG, fixed generator, gen subcommand
jgarzik Oct 12, 2026
9ccff28
tsq diff: random SQL against a SQLite oracle, with shrinking
jgarzik Oct 12, 2026
13a348a
tsq corpus: hand corpora under the same oracle; corpora move to tsq/
jgarzik Oct 12, 2026
caac8fa
Red list: defects found by tsq diff
jgarzik Oct 12, 2026
c3b173c
tsq: say why the oracle's SQLite is built without long double
jgarzik Oct 12, 2026
1a0b40d
tsq: build with the 1.88 MSRV
jgarzik Oct 12, 2026
40dbbe7
Red list: size the tsq performance tests by data volume
jgarzik Oct 12, 2026
9d6decd
Reals: SQLite's double-double conversions, text to real and back
jgarzik Oct 12, 2026
aa1ef73
Arithmetic: % with a real operand and ROUND as SQLite computes them
jgarzik Oct 12, 2026
483f0e6
CONCAT_WS with an empty-string argument: a documented difference
jgarzik Oct 12, 2026
bca20e2
FROM planner: place subquery conjuncts by what they read; test consta…
jgarzik Oct 12, 2026
9251bbc
Windows: aggregates over moving frames step and invert as SQLite does
jgarzik Oct 12, 2026
130b8c8
tsq: the CONCAT_WS rule compares fields, not raw records
jgarzik Oct 12, 2026
97e3ddb
Red test: a CTE read only under a constant-false WHERE is computed an…
jgarzik Oct 12, 2026
7429d16
vm/prepass, engine: compute CTEs and derived tables on first read
jgarzik Oct 12, 2026
c55ea79
tests/defects: a CTE joined to an empty outer loop must never be comp…
jgarzik Oct 12, 2026
0f533f3
vm/compiler_from: open an inner join level's table when the nest firs…
jgarzik Oct 12, 2026
0822978
CLAUDE.md: CTEs and derived tables are computed on first read
jgarzik Oct 12, 2026
554dd4c
tsq: wb, cli and stress subcommands
jgarzik Oct 12, 2026
c62e71a
CLAUDE.md, README, user guide: tsq's subcommands replace the Python t…
jgarzik Oct 12, 2026
0034550
tests/defects: a write error names a Windows file without the \\?\ pr…
jgarzik Oct 12, 2026
d187a3f
tests/defects: SELECT * and a one-cell UPDATE -w on a wide table run …
jgarzik Oct 12, 2026
0b63cd5
table, csv_handler: wide tables cost linear time per row
jgarzik Oct 12, 2026
3248f42
vm/compiler: resolve USING columns and bound columns of wide rows in …
jgarzik Oct 12, 2026
14bd213
error, file_handler: show a canonical Windows path without the \\?\ p…
jgarzik Oct 12, 2026
cca37d2
tsq/cli: the wide scenario draws 10000-column tables again
jgarzik Oct 12, 2026
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
22 changes: 16 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ Sqawk is an SQL-based CLI tool for processing delimiter-separated files (CSV, TS

### Core Components

- **`main.rs`**: Entry point orchestrating the pipeline: CLI parsing → file loading → SQL execution → output → optional writeback
- **`main.rs`**: Entry point orchestrating the pipeline: CLI parsing → file loading → SQL execution → output → optional writeback. Any error is printed once as `sqawk: <message>` (one line, exit 1); clap usage errors exit 2
- **`cli.rs`**: clap arguments; `-s`/`-f` are merged into one ordered script by command-line position
- **`error.rs`**: `SqawkError`; each `Display` is one complete line and no variant exposes a `source()` (that would print the cause twice). File I/O errors name the user's file (`SqawkError::open` / `write`), never a temp file
- **`diag.rs`**: `diag!` / `outln!` -- every stderr diagnostic (errors, `-v`) and REPL stdout line goes through them; write errors are ignored, so a closed stream never panics. Do not use `eprintln!`/`println!`
- **`database.rs`**: Central store for in-memory tables, manages table definitions and schemas
- **`table.rs`**: In-memory table representation with columns, rows, and projection capabilities
- **`sql_executor.rs`**: SQL parsing (via `sqlparser` crate) and execution against in-memory tables
Expand Down Expand Up @@ -84,6 +87,11 @@ SQL execution uses a bytecode VM inspired by SQLite's architecture:
(RETURNING, ON CONFLICT, UPDATE FROM, WITH, QUALIFY, ...) before
compilation; INSERT/UPDATE/DELETE/Query/Select are destructured
exhaustively so a new sqlparser field fails to compile until handled
- `parse.rs`: `parse_sql`, the only SQL-to-AST entry point: parses with
`dialect.rs`'s `SqawkDialect` (Hive syntax, SQLite operator precedence),
rewrites compound SELECTs left-deep (equal precedence, left to right), and
enforces SQLite's limits (expression depth 1000, 500 compound terms) with an
iterative walk before anything recurses over the tree
- `ast_compat.rs`: thin accessors normalizing sqlparser AST shapes
- `engine.rs`: Executes bytecode instructions
- `arith.rs`: SQLite numeric semantics -- text-to-number coercion, i64
Expand All @@ -104,10 +112,12 @@ effects of the ones before it.

- **`file_handler.rs`**: Manages loading files into database tables
- **`csv_handler.rs`**: The single loader (`read_table`, built on the `csv`
crate) for every delimiter and for stdin, plus writeback. `.csv` files always
have a header; other files keep the header guess and `#`-comment skipping.
Fields load as Integer/Float only when the text round-trips exactly, so
untouched cells write back byte-for-byte
crate) for every delimiter and for stdin, plus writeback. `.tsv`/`.tab` are
tab-separated, everything else comma (unless `-F`); the first line is always
the header unless `--no-header`/`--tabledef`; `#` lines are comments only
under `--comments`. Each table keeps its file's bytes (`RawRows` in
`table.rs`: one buffer + per-row offsets), so untouched rows write back
byte-for-byte; every row mutation in `table.rs` keeps the offsets in sync

### SQL Features

Expand All @@ -122,7 +132,7 @@ follow SQLite 3.45; deliberate differences are listed in

### Safe Writeback Model

By default, all modifications remain in memory only. The `--write` flag must be explicitly provided to save changes back to source files. Only tables modified by INSERT/UPDATE/DELETE are written.
By default, all modifications remain in memory only. The `--write` flag must be explicitly provided to save changes back to source files. Only tables changed by INSERT/UPDATE/DELETE/ALTER/TRUNCATE are written, and rows a statement did not change are written back byte for byte. Scratch tables (CREATE TABLE without LOCATION, CREATE TABLE AS) are never written. Every target is checked for writability before any file is written; a read-only file aborts the whole writeback. An open transaction is rolled back before writeback.

## Test Organization

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ exclude = [
clap = { version = "4", features = ["derive"] }
sqlparser = "0.62"
csv = "1"
anyhow = "1"
regex = "1"
tempfile = "3"

rustyline = "15"
# Runs sqawk on a large stack (see MAIN_STACK_BYTES in main.rs); already in
# the build through sqlparser -> recursive.
stacker = "0.1"

[dev-dependencies]
assert_cmd = "2"
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Sqawk is an SQL-based command-line tool for processing delimiter-separated files
- **Window Functions** - ROW_NUMBER, RANK, DENSE_RANK, PERCENT_RANK, CUME_DIST, NTILE, LAG, LEAD, FIRST_VALUE, LAST_VALUE, NTH_VALUE, and aggregates with `OVER (PARTITION BY ... ORDER BY ...)`, ROWS frames and named windows
- **DDL** - CREATE TABLE, CREATE TABLE AS SELECT, DROP, ALTER TABLE ADD COLUMN, TRUNCATE
- **Expressions** - CASE, CAST, BETWEEN, IN, LIKE/ILIKE ... ESCAPE, IS [NOT] DISTINCT FROM, `||`, arithmetic; ORDER BY ... NULLS FIRST/LAST
- **File Formats** - CSV, TSV, and custom delimiters; headerless files via `--no-header` or `--tabledef`; unchanged cells survive `--write` byte for byte
- **File Formats** - CSV, TSV (`.tsv`/`.tab`), and custom delimiters (`-F`); headerless files via `--no-header` or `--tabledef`; `#` comment lines via `--comments`; untouched rows survive `--write` byte for byte
- **Pipelines** - Reads standard input with a `-` file operand
- **Safe by Default** - Files unchanged unless `--write` flag is specified
- **Interactive REPL** - Explore data interactively with `-i` flag
Expand Down Expand Up @@ -47,8 +47,14 @@ sqawk -s "UPDATE data SET status = 'archived' WHERE year < 2020" data.csv --writ

# Read standard input as the table "stdin"
cat employees.csv | sqawk -s "SELECT name FROM stdin WHERE salary > 70000" -

# Run the statements in a file; print tab-separated results without a header
sqawk -f report.sql --output-delimiter '\t' --no-output-header employees.csv
```

Errors are one line on stderr (`sqawk: no such column: nmae`) with exit
status 1; usage errors exit with status 2.

## `tsq` - test data generator (development only)

The source tree also contains `tsq`, which generates deterministic multi-table
Expand Down
119 changes: 83 additions & 36 deletions doc/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,49 @@ Tables maintain an internal mapping of column names to their indices for efficie
logic, so a comparison involving NULL is UNKNOWN and its row is filtered out
rather than raising an error. Sorting and grouping treat NULL as equal to
NULL, which SQL comparison must not — the two use separate paths deliberately
- **Coercion**: A comparison mixing a number and a string converts the string
when it parses as a number, and compares textually otherwise. See
[SQL Reference](sql_reference.md) for the exact rules
- **Comparison**: One rule everywhere (`=`, `<`, IN lists and subqueries,
joins, ORDER BY, MIN/MAX): two values that both read as numbers -- numbers,
or text spelling a number such as `'007'` or `'1e3'` -- compare as numbers,
text against text included (`'10' > '9'`); numbers sort before other text;
other text compares byte by byte. DISTINCT, GROUP BY and the compound
operators still keep text `'1'` and integer `1` apart. See
[SQL Reference](sql_reference.md#type-coercion) for the exact rules

### Table Lifecycle

Tables are loaded from delimiter-separated files at startup and can be modified through SQL operations. Sqawk uses a safe, sed-like writeback model:

1. **Default Behavior**: All modifications remain in memory only
2. **Tracking Changes**: The system tracks which tables have been modified by any operation
2. **Tracking Changes**: The database keeps the set of modified tables
(`Database::modified`): INSERT, UPDATE, DELETE, ALTER TABLE and TRUNCATE
mark a table, CREATE TABLE marks the new one, and DROP TABLE removes the
mark with the table
3. **Write on Exit**: Modified tables are only written back to their source files if:
- The `--write` (or `-w`) flag is explicitly provided
- The table was actually modified by an SQL operation (INSERT, UPDATE, DELETE)
- The table was actually modified by one of those statements
- The table has a file: a scratch table (CREATE TABLE without LOCATION,
CREATE TABLE ... AS) is skipped. A table read from standard input is an
error instead
4. **Safe Execution**: Without the `--write` flag, source files remain untouched regardless of operations performed
5. **Write Only Modified**: Only tables that were changed are written; unmodified tables are not rewritten
6. **Format Preservation**: Original file formats and delimiters are preserved during writeback
6. **Check First**: Every target is checked before any file is written; a
read-only file, or a directory that cannot take the temp file, fails the
run with no file written
7. **Format Preservation**: Original file formats and delimiters are preserved during writeback

### Transactions

`BEGIN` / `COMMIT` (`END`) / `ROLLBACK` are handled in `vm/mod.rs` before
compilation and act on the `Database` directly. The undo state is
copy-on-write: `BEGIN` records only the modified-table set; the first
`get_table_mut` of a table inside the transaction stores a deep copy of it
(rows, source bytes, layout), `remove_table` moves the dropped table into
the undo state, and `add_table` records that the name did not exist.
`ROLLBACK` puts every recorded table back and restores the modified set;
`COMMIT` discards the undo state. A transaction that only reads copies
nothing, and one that changes a table copies it once. A transaction still
open at the end of the run (or REPL session) is rolled back before writeback.
Savepoints are not supported.

This design ensures that users can experiment with data manipulations while maintaining the integrity of source files. The verbose mode (`-v`) provides additional confirmation about whether changes were saved or not.

Expand Down Expand Up @@ -177,15 +204,20 @@ lasts only for that statement.

### File Handlers

- `FileHandler` (`file_handler.rs`) resolves `[table_name=]path` operands,
chooses the delimiter (`-F`, else comma for `.csv`, no extension and stdin,
tab otherwise) and the header policy (`.csv` and stdin: header always;
other files: headerless guess and `#` comments; `--no-header`/`--tabledef`:
no header), and routes writeback.
- `FileHandler` (`file_handler.rs`) resolves `[table_name=]path` operands
(`parse_table_spec`: the prefix names the table only when it is
identifier-like), records each file's canonical absolute path (so the same
file cannot be loaded twice, and REPL `.cd` does not change what `.save`
writes), chooses the delimiter (`-F`, else tab for `.tsv`/`.tab`, comma for
everything else and stdin) and the header policy (the first line is the
header unless `--no-header`/`--tabledef`; `#` lines are comments only
under `--comments`), and routes writeback.
- `csv_handler.rs` does all parsing and writing. Every format is read by the
`csv` crate with one configuration (RFC 4180 quoting, multi-line quoted
fields, no trimming, BOM stripped, ragged rows padded), and written back by
a small RFC 4180 record writer that also quotes fields with leading or
fields, no trimming, ragged rows padded, an unclosed quote is an error,
header names must be non-empty and unique ignoring case). Untouched rows
are written back as their source bytes; changed and inserted rows by a
small RFC 4180 record writer that also quotes fields with leading or
trailing whitespace.

## File I/O Patterns
Expand All @@ -205,7 +237,9 @@ This pattern provides several safety guarantees:
- **Disk Full Protection**: If the disk runs out of space, the original file is preserved
- **All-or-Nothing Semantics**: Either the complete new file exists, or the original file is unchanged

The temporary file is created in the same directory as the target file to ensure the rename operation is atomic (same filesystem requirement on POSIX systems). A symlinked path is resolved first, so the link's target is rewritten and the link itself is left in place.
The temporary file is created in the same directory as the target file to ensure the rename operation is atomic (same filesystem requirement on POSIX systems). A symlinked path is resolved first, so the link's target is rewritten and the link itself is left in place. The rename replaces the directory entry, so a file with several hard links is split from them: the other links keep the old contents.

Because the rename would replace even a read-only file, `csv_handler::check_writable` runs for every target before anything is written: a file whose mode is read-only, or that cannot be opened for appending, is refused (`cannot write 'x.csv': file is read-only`), and a temp file is created and removed in its directory to prove the rename can happen.

### Delimiter Consistency

Expand All @@ -223,17 +257,26 @@ Sqawk supports two primary output modes:
1. **Stdout Output**: Query results (SELECT) are written to standard output using
the `-F` delimiter, or comma when `-F` was not given. This is independent of
the delimiter a source table was parsed with
2. **File Writeback**: Modified tables (via INSERT, UPDATE, DELETE) are written back to their source files when `--write` is specified, preserving that file's delimiter
2. **File Writeback**: Modified tables (via INSERT, UPDATE, DELETE, ALTER TABLE, TRUNCATE) are written back to their source files when `--write` is specified, preserving that file's delimiter

For stdout output, the data is streamed directly without buffering concerns. For file writeback, the atomic write pattern described above is used.

## Storage

Every table holds its rows in memory as owned values (`Vec<Row>`). Files are
read whole and parsed by the `csv` crate -- RFC 4180 quoting, quoted fields
spanning lines, no trimming, UTF-8 byte order mark stripped -- and each cell's
type is inferred with a rule that round-trips (see "Type inference" below), so
an unmodified cell is written back byte for byte.
spanning lines, no trimming, UTF-8 byte order mark set aside -- and each
cell's type is inferred with a rule that round-trips (see "Type inference"
below), so an unmodified cell of a changed row keeps its text.

A loaded table also keeps the file's bytes (`RawRows` in `table.rs`): the
whole file in one buffer plus a start/end offset per row, parallel to the
rows. That costs the file's size plus 16 bytes per row. A row keeps its
offsets while no statement changes it -- an UPDATE that sets every column to
an identical value (same type, same value) counts as no change -- and is
written back as exactly those bytes. INSERT adds rows without source bytes;
DELETE drops a row's offsets with it; ALTER TABLE ADD COLUMN and TRUNCATE
discard the source bytes of every row (every line changes).

Standard input (the `-` operand) is read the same way, directly from the pipe.
The resulting table has no file path, so `--write` reports it as unwritable
Expand All @@ -244,35 +287,38 @@ rather than writing anywhere.
- empty field: NULL
- canonical integer (`42`, `-7`, where formatting the number back gives the
same text): INTEGER
- canonical float (`1.5`, `-0.25`): REAL
- anything else, verbatim: TEXT -- including `007`, `1.50`, `1e3`, ` 42`,
`true`, `NaN`
- canonical float (`1.5`, `-0.25`, `2.0`, `1.0e+20`, where printing the real
the way SQLite's `CAST(x AS TEXT)` does gives the same text): REAL
- anything else, verbatim: TEXT -- including `007`, `1.50`, `1e3`, `-0.0`,
`0.30000000000000004`, ` 42`, `true`, `NaN`

Text that spells a number still acts as one where a number is needed:
arithmetic, `SUM`/`AVG`, comparison against a number, and sort order.
arithmetic, `SUM`/`AVG`, every comparison (against a number or against other
numeric text), and sort order.

### Source layout

Each table records how its file was laid out -- whether it had a header line,
whether records ended in CRLF, and whether `#` lines were comments -- and
writeback reproduces it. Writeback also keeps the file's permissions and, for
a symlink, rewrites the link's target and leaves the link in place.

In a file read with comment skipping (any delimited file that is not `.csv`),
each `#` comment line is kept verbatim, with its own line ending, and
anchored to the data that follows it: comments before the header (or, in a
headerless file, before the first row) stay at the top, comments before a
row stay with that row, and comments after the last row stay at the end. So
a no-op UPDATE writes such a file back byte for byte. When rows change:
a UTF-8 byte order mark, whether records ended in CRLF, and whether `#` lines
were comments -- and writeback reproduces it. Writeback also keeps the file's
permissions and, for a symlink, rewrites the link's target and leaves the
link in place. When the file's last line had no line ending and a row is
written after it, the file's line ending is inserted first.

The lines between records -- blank lines, and under `--comments` each `#`
comment line -- are kept verbatim, with their own line endings, and anchored
to the data that follows: lines before the header (or, in a headerless file,
before the first row) stay at the top, lines before a row stay with that
row, and lines after the last row stay at the end. So a no-op UPDATE writes
any file back byte for byte. When rows change:

- UPDATE replaces rows in place, so every comment stays where it was.
- DELETE leaves a deleted row's comments where they were: in front of the
next surviving row, or of the trailing comments. Deleting every row (or
TRUNCATE) keeps all comments, in order.
- INSERT appends rows after the last row and before the trailing comments.

Blank lines are not kept (the reader skips them), and `#` lines in a `.csv`
file are data, not comments.
Without `--comments`, `#` lines are data, not comments.

### Row Identity Across Updates

Expand Down Expand Up @@ -341,8 +387,9 @@ The database engine has several architectural limitations:

- **No Index Structure**: Tables are scanned in full; the only index is the
temporary hash index an equi-join builds for its inner table
- **No Transaction Support**: A statement's modifications are applied once it
completes; there is no BEGIN/COMMIT and no rollback across statements
- **Transactions without savepoints**: BEGIN / COMMIT / ROLLBACK are
supported (see "Transactions" above); SAVEPOINT, RELEASE and ROLLBACK TO
are not
- **Schema Flexibility**: Types are inferred per value rather than enforced
- **No Constraints System**: Referential integrity not enforced
- **Whole-dataset residency**: A table must fit in memory
Expand Down
Loading
Loading