This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Overview
Relevant source files
Purpose and Scope
The sqlite-sqllogictest-corpus repository provides a Git-based mirror of SQLite's official sqllogictest corpus, which is maintained in a Fossil repository. This repository automates the extraction and synchronization of test files, making them accessible to downstream consumers who prefer Git-based workflows over Fossil-based access.
Scope : This page introduces the repository's purpose, architecture components, and automation mechanisms. For detailed information about specific subsystems:
- System architecture and extraction pipeline: see System Architecture
- Test corpus organization and categories: see Test Corpus Reference
- Cross-database compatibility mechanisms: see Cross-Database Compatibility
- Practical usage instructions: see Usage Guide
- Test file format details: see Test File Format Specification
Repository Purpose
This repository serves as a distribution mechanism that:
| Function | Description |
|---|---|
| Mirrors upstream source | Clones SQLite's Fossil-hosted sqllogictest repository |
| Converts to Git format | Extracts test files and commits them to a Git repository |
| Automates synchronization | Updates corpus weekly via GitHub Actions |
| Provides accessible distribution | Enables Git-based access for database vendors, developers, and CI/CD systems |
The repository does not modify or validate test files—it purely extracts and distributes them.
Sources : README.md:1-3 .github/workflows/update-corpus.yml:1-11
System Components
The following diagram maps the repository's components to their implementation in code:
Sources : Dockerfile:1-36 .github/workflows/update-corpus.yml:1-45 README.md:1-27
graph TB
subgraph "Upstream Source"
FOSSIL["https://www.sqlite.org/sqllogictest/\nFossil Repository"]
end
subgraph "Docker Image: slt-gen"
DOCKERFILE["Dockerfile"]
FOSSIL_CLIENT["fossil clone command\nLine 15"]
EXTRACT_SCRIPT["slt-extract script\nLines 20-31"]
end
subgraph "GitHub Actions Workflow"
WORKFLOW_FILE[".github/workflows/update-corpus.yml"]
BUILD_STEP["docker build -t slt-gen\nLine 25"]
EXTRACT_STEP["docker run --rm -v test:/work/test\nLine 31"]
COMMIT_STEP["git commit/push\nLines 36-44"]
end
subgraph "Output"
TEST_DIR["test/ directory"]
EVIDENCE_DIR["test/evidence/"]
INDEX_DIR["test/index/"]
end
FOSSIL -->|Cloned by| FOSSIL_CLIENT
DOCKERFILE -->|Defines| FOSSIL_CLIENT
DOCKERFILE -->|Defines| EXTRACT_SCRIPT
WORKFLOW_FILE -->|Executes| BUILD_STEP
BUILD_STEP -->|Creates| DOCKERFILE
WORKFLOW_FILE -->|Executes| EXTRACT_STEP
EXTRACT_STEP -->|Runs| EXTRACT_SCRIPT
EXTRACT_SCRIPT -->|Writes to| TEST_DIR
TEST_DIR -->|Contains| EVIDENCE_DIR
TEST_DIR -->|Contains| INDEX_DIR
WORKFLOW_FILE -->|Commits| EVIDENCE_DIR
WORKFLOW_FILE -->|Commits| INDEX_DIR
Automation Mechanism
The repository uses a GitHub Actions workflow to maintain synchronization with the upstream Fossil repository:
Key workflow behaviors :
graph LR
subgraph "Triggers"
CRON["Cron Schedule\n0 6 * * 1\nEvery Monday 06:00 UTC"]
MANUAL["workflow_dispatch\nManual trigger"]
PR["pull_request\nValidation on PR"]
end
subgraph "update-corpus.yml Workflow"
CHECKOUT["Checkout repository\nLine 21-22"]
BUILD["Build extractor image\nLine 24-25"]
REFRESH["Refresh corpus\nLines 27-31"]
COMMIT["Commit and push changes\nLines 33-44"]
end
subgraph "Conditional Logic"
CHANGE_CHECK["git status --porcelain\nLine 36"]
EVENT_CHECK["github.event_name != pull_request\nLine 34"]
end
CRON --> CHECKOUT
MANUAL --> CHECKOUT
PR --> CHECKOUT
CHECKOUT --> BUILD
BUILD --> REFRESH
REFRESH --> EVENT_CHECK
EVENT_CHECK -->|Not PR| CHANGE_CHECK
EVENT_CHECK -->|Is PR| SKIP["Skip commit"]
CHANGE_CHECK -->|Changes detected| COMMIT
CHANGE_CHECK -->|No changes| SKIP
| Condition | Action |
|---|---|
| Scheduled run | Commits and pushes changes if corpus updated |
| Manual dispatch | Commits and pushes changes if corpus updated |
| Pull request | Builds and extracts but skips commit step |
| No changes detected | Skips commit and outputs message |
Sources : .github/workflows/update-corpus.yml:3-44
Docker Extraction Process
The slt-gen Docker image performs the extraction:
The slt-extract script [Dockerfile:20-31]] implements a simple copy operation:
- Source:
/src/test(extracted from Fossil) - Destination:
/work/test(mounted from host) - Operation:
cp -Rto recursively copy all files
Sources : Dockerfile:1-36
Test Corpus Structure
The extracted corpus follows this organization:
| Directory | Content Type | Examples |
|---|---|---|
test/evidence/ | SQL language specification tests | DDL, DML, DQL commands |
test/index/ | Query optimization tests | BETWEEN operator tests with various scenarios |
The directory structure mirrors the upstream Fossil repository exactly [\\README.md21](), preserving file paths and organization for compatibility with existing tooling.
For detailed documentation of test organization, see Test Organization Structure.
Sources : README.md:1-27 High-level diagrams
Downstream Use Cases
This repository enables multiple consumption patterns:
Primary use cases :
- SQL Compliance Testing : Database vendors validate their SQL implementations against SQLite's test corpus
- Continuous Integration : CI/CD pipelines incorporate tests for automated regression detection
- Research : Academic and industry researchers study SQL semantics and edge cases
- Development : Developers building SQL engines use tests for validation
For integration examples, see Integrating with CI/CD Systems.
Sources : README.md:1-27 High-level diagrams
References
- Upstream Fossil repository:
https://www.sqlite.org/sqllogictest/ - SQLite sqllogictest documentation:
https://sqlite.org/sqllogictest/doc/trunk/about.wiki - SQLite GitHub mirror:
https://github.com/sqlite/sqlite
Sources : README.md:23-26
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
System Architecture
Relevant source files
Purpose and Scope
This document describes the overall architecture of the sqlite-sqllogictest-corpus system, which provides automated synchronization of SQLite's official sqllogictest corpus from a Fossil repository to a Git repository. The architecture consists of three primary layers: source repository access, Docker-based extraction, and GitHub Actions automation.
For detailed information about specific subsystems:
- Extraction implementation details: see Corpus Extraction Pipeline
- Workflow scheduling and commit logic: see Automated Update Workflow
- Test directory structure: see Test Organization Structure
Architectural Overview
The system implements a scheduled pull-based synchronization model that extracts test files from SQLite's Fossil repository and commits them to a Git repository. The architecture eliminates manual intervention through containerization and scheduled automation.
graph TB
subgraph Source["Fossil Source Repository"]
FOSSIL["sqlite.org/sqllogictest/\nFossil SCM"]
end
subgraph Automation["GitHub Actions Layer"]
WORKFLOW["update-corpus.yml\nWorkflow Definition"]
SCHEDULE["Cron: 0 6 * * 1\nMonday 06:00 UTC"]
DISPATCH["workflow_dispatch\nManual Trigger"]
end
subgraph Container["Docker Build & Extract Layer"]
DOCKERFILE["Dockerfile\nImage Definition"]
IMAGE["slt-gen\nDocker Image"]
EXTRACT["slt-extract\nBash Script"]
end
subgraph Storage["Filesystem Storage"]
TEST_DIR["test/\nMounted Volume"]
EVIDENCE["test/evidence/\nSQL Tests"]
INDEX["test/index/\nOptimization Tests"]
end
subgraph VCS["Version Control"]
REPO["Git Repository\nsqlite-sqllogictest-corpus"]
end
FOSSIL -->|fossil clone| IMAGE
SCHEDULE -->|triggers| WORKFLOW
DISPATCH -->|triggers| WORKFLOW
WORKFLOW -->|docker build -t slt-gen .| DOCKERFILE
DOCKERFILE -->|creates| IMAGE
IMAGE -->|contains| EXTRACT
WORKFLOW -->|docker run --rm -v| IMAGE
IMAGE -->|executes| EXTRACT
EXTRACT -->|cp -R /src/test/.| TEST_DIR
TEST_DIR -->|contains| EVIDENCE
TEST_DIR -->|contains| INDEX
WORKFLOW -->|git add/commit/push| REPO
TEST_DIR -.->|persisted to| REPO
System Components
Sources: .github/workflows/update-corpus.yml:1-44 Dockerfile:1-36 README.md:1-27
Component Architecture
Fossil Repository Access Layer
The system accesses SQLite's canonical sqllogictest repository hosted on Fossil SCM. The repository URL https://www.sqlite.org/sqllogictest/ contains the upstream test corpus that serves as the single source of truth.
graph LR
APT["apt-get install fossil"]
CLONE["fossil clone\nhttps://www.sqlite.org/sqllogictest/"]
FOSSIL_FILE["/src/sqllogictest.fossil\nLocal Clone"]
OPEN["fossil open\n--user root"]
CHECKOUT["/src/test/\nWorking Directory"]
APT -->|installs| CLONE
CLONE -->|creates| FOSSIL_FILE
FOSSIL_FILE -->|input to| OPEN
OPEN -->|populates| CHECKOUT
The Docker image clones this repository during build time using the fossil client installed via Debian's package manager:
Sources: Dockerfile:5-17
Docker Container Architecture
The Dockerfile defines a Debian-based image that encapsulates the entire extraction process. The image is tagged as slt-gen during the build phase.
| Component | Purpose | Implementation |
|---|---|---|
| Base Image | debian:stable-slim | Minimal footprint for build tools |
| Fossil Client | Version control access | Installed via apt-get |
| Working Directory | /work | Volume mount point for output |
| Source Directory | /src | Contains cloned Fossil repository |
| Extraction Script | /usr/local/bin/slt-extract | Bash script that copies test files |
graph TD
ENTRY["ENTRYPOINT slt-extract"]
ARG["dest_root=${1:-/work/test}"]
SRC["src_root=/src/test"]
MKDIR["mkdir -p $dest_root"]
COPY["cp -R $src_root/. $dest_root/"]
ECHO["echo copied corpus..."]
ENTRY -->|executes with argument| ARG
ARG --> SRC
SRC --> MKDIR
MKDIR --> COPY
COPY --> ECHO
The slt-extract script serves as the container's entrypoint and accepts a single optional argument for the destination directory (defaults to /work/test):
Sources: Dockerfile:1-36 Dockerfile:20-35
GitHub Actions Workflow Layer
The update-corpus.yml workflow orchestrates the entire update cycle through four discrete steps executed on an ubuntu-latest runner.
graph TD
CRON["schedule:\ncron: 0 6 * * 1"]
MANUAL["workflow_dispatch:\nManual Execution"]
PR["pull_request:\npaths filter"]
WORKFLOW["update job\nruns-on: ubuntu-latest"]
CRON -->|Monday 06:00 UTC| WORKFLOW
MANUAL -->|on-demand| WORKFLOW
PR -->|when specific files change| WORKFLOW
Workflow Triggers
The workflow monitors changes to three specific paths in pull requests: .github/workflows/update-corpus.yml, Dockerfile, and README.md.
Sources: .github/workflows/update-corpus.yml:3-12
Execution Steps
The workflow executes four sequential steps:
- Repository Checkout - Uses
actions/checkout@v4to clone the Git repository - Image Build - Executes
docker build -t slt-gen .to create the extraction container - Corpus Refresh - Removes existing
test/directory, recreates it, and runs the container with volume mount - Change Commit - Conditionally commits and pushes changes if files were modified and the event is not a pull request
Sources: .github/workflows/update-corpus.yml:18-44
sequenceDiagram
participant Runner as "ubuntu-latest runner"
participant Git as "Git Repository"
participant Docker as "Docker Engine"
participant Volume as "test/ directory"
Runner->>Git: actions/checkout@v4
Git-->>Runner: Repository checked out
Runner->>Docker: docker build -t slt-gen .
Docker-->>Runner: Image slt-gen created
Runner->>Volume: rm -rf test
Runner->>Volume: mkdir test
Runner->>Docker: docker run --rm -v $PWD/test:/work/test slt-gen
Docker->>Volume: Extract files to mounted volume
Docker-->>Runner: Container exit
Runner->>Git: git status --porcelain
alt changes detected and not PR
Runner->>Git: git add test
Runner->>Git: git commit -m "Update sqllogictest corpus"
Runner->>Git: git push
else no changes or is PR
Runner->>Runner: echo "No updates; skipping commit"
end
Conditional Commit Logic
The workflow implements intelligent change detection to avoid empty commits:
This conditional executes only when github.event_name != 'pull_request', preventing commits during PR validation runs.
Sources: .github/workflows/update-corpus.yml:34-44
Data Flow Architecture
The complete data flow follows a unidirectional path from the Fossil repository through Docker containerization to the Git repository.
Sources: Dockerfile:14-28 .github/workflows/update-corpus.yml:24-41
flowchart LR
subgraph Upstream
FOSSIL_SRC["sqlite.org/sqllogictest\nFossil Repository"]
end
subgraph "Build Phase"
DOCKER_BUILD["docker build -t slt-gen ."]
FOSSIL_CLONE["fossil clone + fossil open"]
SRC_TEST["/src/test/\nFiles in Image"]
end
subgraph "Extract Phase"
DOCKER_RUN["docker run --rm -v"]
SLT_EXTRACT["slt-extract script"]
CP_COMMAND["cp -R /src/test/. /work/test/"]
end
subgraph "Persist Phase"
MOUNTED_VOL["$PWD/test/\nHost Filesystem"]
GIT_ADD["git add test"]
GIT_COMMIT["git commit"]
GIT_PUSH["git push"]
end
subgraph Downstream
GIT_REMOTE["GitHub Repository\nsqlite-sqllogictest-corpus"]
end
FOSSIL_SRC -->|cloned during build| FOSSIL_CLONE
DOCKER_BUILD --> FOSSIL_CLONE
FOSSIL_CLONE --> SRC_TEST
SRC_TEST -.->|embedded in image| DOCKER_RUN
DOCKER_RUN --> SLT_EXTRACT
SLT_EXTRACT --> CP_COMMAND
CP_COMMAND -->|volume mount| MOUNTED_VOL
MOUNTED_VOL --> GIT_ADD
GIT_ADD --> GIT_COMMIT
GIT_COMMIT --> GIT_PUSH
GIT_PUSH --> GIT_REMOTE
Volume Mount Strategy
The system uses Docker volume mounts to bridge container execution with host filesystem persistence. The mount configuration "$PWD/test:/work/test" maps the host's current working directory test/ subdirectory to the container's /work/test path.
This approach ensures:
- Test files persist after container termination (
--rmflag removes container but preserves mounted volume data) - The workflow can detect changes using
git status --porcelain - No data copying between container and host required after extraction
Sources: .github/workflows/update-corpus.yml31 Dockerfile:24-28
Build-Time vs. Runtime Execution
The architecture separates concerns between build-time and runtime operations:
Build-Time Operations
Executed during docker build -t slt-gen .:
- Installation of system dependencies (
fossil,bash,build-essential) - Cloning of Fossil repository to
/src/sqllogictest.fossil - Opening Fossil repository to
/srcworking directory - Creation of
/usr/local/bin/slt-extractscript - Setting script executable permissions
Runtime Operations
Executed during docker run --rm -v "$PWD/test:/work/test" slt-gen:
- Execution of
slt-extractentrypoint - Directory creation:
mkdir -p /work/test - Recursive copy:
cp -R /src/test/. /work/test/ - Status output:
echo "copied corpus to /work/test"
This separation ensures the expensive Fossil clone operation occurs once during image build, while extraction can execute repeatedly with minimal overhead.
Sources: Dockerfile:5-35 .github/workflows/update-corpus.yml:28-31
System Permissions and Security
The workflow requires write access to the repository through the contents: write permission, enabling automated commits. The commit author is configured as github-actions[bot] with email 41898282+github-actions[bot]@users.noreply.github.com.
The Fossil repository is configured with default user root to avoid interactive prompts during clone and open operations.
Sources: .github/workflows/update-corpus.yml:14-15 .github/workflows/update-corpus.yml:37-38 Dockerfile:15-17
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Corpus Extraction Pipeline
Relevant source files
Purpose and Scope
This document describes the corpus extraction pipeline—the Docker-based system that clones SQLite's official Fossil repository and extracts the sqllogictest corpus into a local filesystem directory. The pipeline consists of a Dockerfile that builds a specialized container image and a bash script (slt-extract) that performs the actual extraction.
For information about the automated workflow that orchestrates this pipeline on a schedule, see Automated Update Workflow. For details on how the extracted tests are organized, see Test Organization Structure.
Pipeline Architecture
The extraction pipeline follows a three-stage architecture: image build, repository cloning, and corpus extraction. The entire process is encapsulated within a Docker container to ensure reproducibility and isolation from the host environment.
Sources: Dockerfile:1-36
flowchart LR
subgraph "Build Stage"
DF["Dockerfile"]
BASE["debian:stable-slim\nbase image"]
DEPS["Dependencies:\nfossil, bash, tcl,\nbuild-essential"]
end
subgraph "Clone Stage"
FOSSIL_CMD["fossil clone\nwww.sqlite.org/sqllogictest"]
FOSSIL_OPEN["fossil open\nsqllogictest.fossil"]
SRC_DIR["/src/test/\ncloned corpus"]
end
subgraph "Extract Stage"
SCRIPT["/usr/local/bin/\nslt-extract"]
CP_CMD["cp -R /src/test\nto /work/test"]
DEST_DIR["/work/test/\nextracted corpus"]
end
DF --> BASE
DF --> DEPS
DEPS --> FOSSIL_CMD
FOSSIL_CMD --> FOSSIL_OPEN
FOSSIL_OPEN --> SRC_DIR
DF --> SCRIPT
SCRIPT --> CP_CMD
SRC_DIR --> CP_CMD
CP_CMD --> DEST_DIR
Docker Image Construction
The extraction pipeline uses a Debian-based Docker image defined in the Dockerfile. The image is built in multiple logical stages, though implemented as a single-stage Dockerfile for simplicity.
Base Image and Dependencies
The image starts from debian:stable-slim and installs the required packages:
| Package | Purpose |
|---|---|
fossil | Fossil SCM client for cloning the repository |
bash | Shell for executing the extraction script |
tcl | Required by some fossil operations |
build-essential | Compilation tools (legacy requirement) |
ca-certificates | HTTPS certificate validation |
curl | HTTP client utilities |
The package installation occurs at Dockerfile:5-12 using apt-get with --no-install-recommends to minimize image size. The /var/lib/apt/lists/* cache is removed after installation to further reduce the final image size.
Fossil Repository Cloning
The repository cloning process occurs during the Docker build phase, not at runtime. This design decision ensures that the cloned repository is baked into the image, eliminating the need for network access during extraction.
sequenceDiagram
participant Build as Docker Build
participant Fossil as fossil CLI
participant Remote as www.sqlite.org
participant FS as /src filesystem
Build->>FS: WORKDIR /src
Build->>Fossil: fossil clone https://www.sqlite.org/sqllogictest/
Fossil->>Remote: HTTP GET sqllogictest repository
Remote-->>Fossil: repository data
Fossil->>FS: write /src/sqllogictest.fossil
Build->>Fossil: fossil open sqllogictest.fossil
Fossil->>FS: extract to /src/test/
Build->>Fossil: fossil user default root
Fossil->>FS: set default user
The cloning sequence at Dockerfile:14-17 performs three operations:
fossil clone: Downloads the repository fromhttps://www.sqlite.org/sqllogictest/to/src/sqllogictest.fossilfossil open: Extracts the repository contents to the current working directory (/src)fossil user default root: Sets the default user torootfor subsequent operations
The --user root flag in both the clone and open commands ensures consistent user attribution within the Fossil repository.
Sources: Dockerfile:14-17
Extraction Script Implementation
The extraction logic is implemented in a bash script embedded directly in the Dockerfile as a heredoc. The script is written to /usr/local/bin/slt-extract at Dockerfile:20-31 and marked executable at Dockerfile33
Script Components
Script Variables:
| Variable | Default Value | Description |
|---|---|---|
src_root | /src/test | Source directory containing cloned corpus |
dest_root | ${1:-/work/test} | Destination directory (first argument or default) |
The script uses bash strict mode (set -euo pipefail) at Dockerfile22 to ensure:
-e: Exit immediately if any command fails-u: Treat unset variables as errors-o pipefail: Return the exit status of the last failed command in a pipe
Extraction Operation
The core extraction operation at Dockerfile28 uses cp -R with the trailing /. syntax:
cp -R "$src_root/." "$dest_root/"
This syntax copies the contents of /src/test/ (including hidden files) rather than the directory itself, resulting in the corpus files appearing directly under dest_root rather than in a nested test/ subdirectory.
Sources: Dockerfile:20-35
Container Execution Model
The container is configured with slt-extract as the ENTRYPOINT at Dockerfile35 This design allows the container to function as a single-purpose executable tool.
flowchart LR
subgraph "Host Filesystem"
HOST_TEST["$PWD/test/"]
end
subgraph "Container Filesystem"
CONTAINER_WORK["/work/test/"]
SRC_TEST["/src/test/\n(baked into image)"]
end
HOST_TEST -.->|-v mount| CONTAINER_WORK
SRC_TEST -->|cp -R| CONTAINER_WORK
CONTAINER_WORK -->|persists to| HOST_TEST
Volume Mounting Strategy
The extraction process relies on Docker volume mounting to persist the extracted corpus to the host filesystem:
When the container runs with -v "$PWD/test:/work/test", the host's test/ directory is mounted at /work/test inside the container. The slt-extract script copies from the image's /src/test to the mounted /work/test, making the files appear in the host's test/ directory.
Sources: README.md18 Dockerfile35
Build and Execution Workflow
The complete extraction workflow consists of building the image and running the container:
Image Build Process
The docker build command at README.md10 creates an image tagged as slt-gen. This tag is referenced in the GitHub Actions workflow (see Automated Update Workflow) and in local usage.
Build stages:
- Pull
debian:stable-slimbase image - Install system packages via
apt-get - Clone Fossil repository from
www.sqlite.org - Extract repository to
/src/test - Write
slt-extractscript to/usr/local/bin - Set script as executable
- Configure container entrypoint
Extraction Execution
The extraction sequence at README.md:16-18 performs:
- Remove existing directory :
rm -rf testensures a clean slate - Create empty directory :
mkdir testcreates the mount point - Run container :
docker runexecutes the extraction with:--rm: Remove container after execution-v "$PWD/test:/work/test": Mount host directory into containerslt-gen: Image name
The container executes slt-extract (the entrypoint), which copies corpus files from /src/test to /work/test, persisting them to the host's test/ directory via the volume mount.
Sources: README.md:6-19 Dockerfile35
graph TB
subgraph "Upstream Fossil Repository"
FOSSIL_ROOT["www.sqlite.org/sqllogictest/"]
FOSSIL_TEST["test/ directory"]
FOSSIL_EVIDENCE["test/evidence/"]
FOSSIL_INDEX["test/index/"]
end
subgraph "Docker Image /src"
IMG_SRC["/src/sqllogictest.fossil"]
IMG_TEST["/src/test/"]
IMG_EV["/src/test/evidence/"]
IMG_IDX["/src/test/index/"]
end
subgraph "Container /work"
WORK_TEST["/work/test/"]
WORK_EV["/work/test/evidence/"]
WORK_IDX["/work/test/index/"]
end
subgraph "Host Filesystem"
HOST_TEST["$PWD/test/"]
HOST_EV["$PWD/test/evidence/"]
HOST_IDX["$PWD/test/index/"]
end
FOSSIL_ROOT --> FOSSIL_TEST
FOSSIL_TEST --> FOSSIL_EVIDENCE
FOSSIL_TEST --> FOSSIL_INDEX
FOSSIL_ROOT -->|fossil clone| IMG_SRC
IMG_SRC -->|fossil open| IMG_TEST
IMG_TEST --> IMG_EV
IMG_TEST --> IMG_IDX
IMG_TEST -->|cp -R| WORK_TEST
IMG_EV -->|cp -R| WORK_EV
IMG_IDX -->|cp -R| WORK_IDX
WORK_TEST -->|volume mount| HOST_TEST
WORK_EV -->|volume mount| HOST_EV
WORK_IDX -->|volume mount| HOST_IDX
Directory Structure Mapping
The extraction pipeline maintains a direct mirror of the upstream repository structure:
The pipeline preserves the complete directory hierarchy from upstream, including:
- SQL language specification tests in
evidence/ - Query optimization tests in
index/ - All subdirectory structures and file naming conventions
Sources: Dockerfile:14-28 README.md21
Error Handling and Safety
The extraction script includes several safety mechanisms:
| Mechanism | Location | Purpose |
|---|---|---|
set -e | Dockerfile22 | Exit on any command failure |
set -u | Dockerfile22 | Exit on undefined variable usage |
set -o pipefail | Dockerfile22 | Propagate pipe failures |
mkdir -p | Dockerfile27 | Create directory if missing, no error if exists |
--rm flag | README.md18 | Clean up container after execution |
The bash strict mode ensures that any failure in the extraction process (e.g., missing source directory, write permission issues) causes immediate script termination with a non-zero exit code, which propagates to the Docker container exit status.
Sources: Dockerfile:20-31 README.md18
Performance Characteristics
The extraction pipeline exhibits the following performance characteristics:
Build Time: The docker build step includes network I/O to clone the Fossil repository. Repository size is typically 50-200 MB, making build time network-dependent (1-5 minutes on typical connections).
Extraction Time: The docker run step performs only filesystem copy operations from the image to the mounted volume. With typical corpus sizes (10,000+ test files), extraction completes in seconds.
Storage Efficiency: The Fossil repository is cloned once during image build and reused for all subsequent extractions. This avoids redundant network operations when running multiple extractions.
The pipeline's design optimizes for extraction speed at the cost of larger image size. This trade-off is appropriate for automated CI/CD environments where fast extraction is critical.
Sources: Dockerfile:15-17 Dockerfile28
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Automated Update Workflow
Relevant source files
Purpose and Scope
This document describes the GitHub Actions workflow that automatically synchronizes the test corpus from SQLite's Fossil repository on a weekly basis. The workflow orchestrates the Docker-based extraction process (detailed in Corpus Extraction Pipeline) and manages the commit lifecycle to keep the Git repository up-to-date with the upstream source.
For information about the extracted test file organization, see Test Organization Structure. For practical usage of the extraction system outside of automation, see Building and Running the Extractor.
Workflow Triggers
The workflow defined in .github/workflows/update-corpus.yml:3-12 supports three distinct trigger mechanisms:
| Trigger Type | Configuration | Purpose |
|---|---|---|
| Scheduled | cron: "0 6 * * 1" | Automated weekly refresh every Monday at 06:00 UTC |
| Manual | workflow_dispatch | On-demand corpus updates triggered through GitHub UI |
| Pull Request | Paths: workflow file, Dockerfile, README | Validates workflow changes before merge |
The scheduled trigger ensures regular synchronization without manual intervention, while the manual trigger allows immediate updates when upstream changes are known. The pull request trigger enables CI validation when modifying the automation infrastructure itself.
Sources: .github/workflows/update-corpus.yml:3-12
Workflow Architecture
The following diagram maps the GitHub Actions job structure to the actual code entities defined in the workflow file:
Sources: .github/workflows/update-corpus.yml:17-44
graph TB
subgraph "GitHub Actions Workflow: update-corpus.yml"
TRIGGER[/"Trigger Events"/]
subgraph "Job: update"
RUNNER["runs-on: ubuntu-latest"]
STEP1["Step: Checkout repository\nuses: actions/checkout@v4"]
STEP2["Step: Build extractor image\ndocker build -t slt-gen ."]
STEP3["Step: Refresh corpus\nrm -rf test; mkdir test\ndocker run --rm -v PWD/test:/work/test slt-gen"]
STEP4["Step: Commit and push changes\nif: github.event_name != 'pull_request'"]
RUNNER --> STEP1
STEP1 --> STEP2
STEP2 --> STEP3
STEP3 --> STEP4
end
end
TRIGGER -->|schedule workflow_dispatch pull_request| RUNNER
STEP2 -.->|Builds from| DOCKERFILE[Dockerfile]
STEP3 -.->|Writes to| TESTDIR["$PWD/test/"]
STEP4 -.->|Commits| GITREPO["Git Repository"]
style TRIGGER fill:#f9f9f9
style RUNNER fill:#f9f9f9
style STEP1 fill:#f9f9f9
style STEP2 fill:#f9f9f9
style STEP3 fill:#f9f9f9
style STEP4 fill:#f9f9f9
Execution Pipeline
Step 1: Repository Checkout
The workflow begins by checking out the repository using the actions/checkout@v4 action with write permissions enabled through the permissions: contents: write declaration at .github/workflows/update-corpus.yml:14-15 This permission is critical for the final commit step.
Sources: .github/workflows/update-corpus.yml:21-22 .github/workflows/update-corpus.yml:14-15
Step 2: Docker Image Build
The slt-gen image is built from the Dockerfile in the repository root:
The build command at .github/workflows/update-corpus.yml25 creates an image tagged as slt-gen, which contains the Fossil clone of the sqllogictest repository and the slt-extract utility defined at Dockerfile:20-33
Sources: .github/workflows/update-corpus.yml:24-25 Dockerfile:1-36
Step 3: Corpus Extraction
The extraction step performs a clean refresh of the test corpus:
The commands at .github/workflows/update-corpus.yml:28-31 ensure a clean state by removing the existing test/ directory before extraction. The Docker volume mount (-v "$PWD/test:/work/test") allows the container to write directly to the host filesystem, with files persisting after the container terminates.
Sources: .github/workflows/update-corpus.yml:27-31 Dockerfile:20-35
Step 4: Change Detection and Commit
The workflow employs intelligent change detection to avoid unnecessary commits:
stateDiagram-v2
[*] --> CheckEventType : Step 4 executes
CheckEventType --> SkipCommit : if pull_request
CheckEventType --> CheckChanges : if NOT pull_request
CheckChanges --> RunGitStatus : git status --porcelain
RunGitStatus --> EvaluateOutput : Check output
EvaluateOutput --> ConfigureGit : Output non-empty
EvaluateOutput --> LogSkip : Output empty
ConfigureGit --> SetUserName : git config user.name
SetUserName --> SetUserEmail : git config user.email
SetUserEmail --> StageFiles : git add test
StageFiles --> Commit : git commit -m message
Commit --> Push : git push
Push --> [*]
LogSkip --> [*] : Echo skip message
SkipCommit --> [*] : Conditional skipped
The conditional at .github/workflows/update-corpus.yml34 prevents commits during pull request runs, ensuring the workflow can be safely tested without polluting the repository history. The git status --porcelain command at .github/workflows/update-corpus.yml36 produces machine-readable output that, when piped to grep ., will only succeed if changes exist. This pattern prevents empty commits when the upstream corpus has not changed.
The bot credentials configured at .github/workflows/update-corpus.yml:37-38 identify automated commits using the standard GitHub Actions bot user (github-actions[bot]).
Sources: .github/workflows/update-corpus.yml:33-44
File System Operations
The workflow manipulates the following filesystem locations:
| Path | Operation | Purpose |
|---|---|---|
$PWD/test/ | Delete, recreate | Clean slate for extraction |
$PWD/test/ | Docker volume mount | Container write target |
/work/test/ (container) | Mount point | Container-side path for extraction |
/src/test/ (container) | Source directory | Fossil repository test location |
The mapping between container paths and host paths is established through the volume specification: -v "$PWD/test:/work/test" at .github/workflows/update-corpus.yml31
Sources: .github/workflows/update-corpus.yml:28-31 Dockerfile:24-28
Workflow Permissions
The workflow requires elevated permissions to commit and push changes:
This declaration at .github/workflows/update-corpus.yml:14-15 grants the workflow write access to repository contents. Without this permission, the git push operation at .github/workflows/update-corpus.yml41 would fail with an authentication error.
The GITHUB_TOKEN is implicitly available to all workflow steps and provides the authentication credentials needed for the push operation. The token is automatically scoped to the repository and expires when the workflow completes.
Sources: .github/workflows/update-corpus.yml:14-15
graph TB
SHELL["Shell: set -euo pipefail\n(in slt-extract)"] --> DOCKER_BUILD["Docker build failure"]
SHELL --> DOCKER_RUN["Docker run failure"]
SHELL --> GIT_PUSH["Git push failure"]
DOCKER_BUILD --> FAIL["Workflow fails"]
DOCKER_RUN --> FAIL
GIT_PUSH --> FAIL
FAIL --> NOTIFY["GitHub Actions UI shows failure"]
FAIL --> EMAIL["Email notification (if configured)"]
Error Handling
The workflow employs a fail-fast approach through shell options and Docker flags:
The --rm flag at .github/workflows/update-corpus.yml31 ensures container cleanup even if the extraction process fails. The slt-extract script uses set -euo pipefail at Dockerfile22 to halt execution on any command failure, undefined variable access, or pipeline errors.
Sources: .github/workflows/update-corpus.yml31 Dockerfile:20-33
Testing Workflow Modifications
The pull request trigger configuration enables safe testing of workflow changes:
When any of these files are modified in a pull request, the workflow executes but skips the commit step due to the conditional at .github/workflows/update-corpus.yml34 This allows verification that:
- The Docker image builds successfully
- The extraction process completes without errors
- The expected test files are generated
The workflow only triggers on changes to infrastructure files, not on modifications to the extracted corpus itself, preventing unnecessary CI runs.
Sources: .github/workflows/update-corpus.yml:8-12 .github/workflows/update-corpus.yml34
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Test Organization Structure
Relevant source files
- test/evidence/in1.test
- test/evidence/slt_lang_createtrigger.test
- test/index/between/1/slt_good_0.test
Purpose and Scope
This document describes the organizational structure of the SQL Logic Test corpus extracted from SQLite's Fossil repository. It explains how test files are categorized, the directory hierarchy used for storage, and the taxonomy that governs test placement within the test/ directory.
For information about how these tests are extracted from the upstream repository, see Corpus Extraction Pipeline. For details on the file format and directives used within individual test files, see Test File Format Specification.
Test Taxonomy
The corpus employs a two-tier categorization system that separates tests based on their validation purpose:
| Category | Directory | Purpose | Focus Area |
|---|---|---|---|
| Evidence Tests | test/evidence/ | Validate SQL language specification compliance | DDL, DML, DQL correctness |
| Index Tests | test/index/ | Validate query optimization behavior | Index usage, query planning |
Evidence tests ensure that SQL statements execute according to specification, verifying the semantic correctness of database operations. Index tests focus on performance-related behaviors, particularly how the query optimizer utilizes indexes to execute queries efficiently.
Sources: Based on system architecture analysis and repository structure.
graph TB
subgraph "test/ Root Directory"
ROOT["test/"]
subgraph "Evidence Directory"
EVIDENCE["test/evidence/"]
subgraph "DDL Tests"
CREATE_TRIGGER["slt_lang_createtrigger.test"]
CREATE_VIEW["slt_lang_createview.test"]
DROP_INDEX["slt_lang_dropindex.test"]
DROP_TABLE["slt_lang_droptable.test"]
DROP_TRIGGER["slt_lang_droptrigger.test"]
DROP_VIEW["slt_lang_dropview.test"]
REINDEX["slt_lang_reindex.test"]
end
subgraph "DML Tests"
REPLACE["slt_lang_replace.test"]
UPDATE["slt_lang_update.test"]
end
subgraph "DQL Tests"
IN1["in1.test"]
IN2["in2.test"]
AGGFUNC["slt_lang_aggfunc.test"]
end
end
subgraph "Index Directory"
INDEX["test/index/"]
subgraph "BETWEEN Tests"
BETWEEN["between/"]
BETWEEN_1["between/1/"]
BETWEEN_10["between/10/"]
BETWEEN_100["between/100/"]
BETWEEN_1_FILE["slt_good_0.test"]
BETWEEN_10_FILES["slt_good_0.test through slt_good_5.test"]
BETWEEN_100_FILE["slt_good_0.test"]
end
end
end
ROOT --> EVIDENCE
ROOT --> INDEX
EVIDENCE --> CREATE_TRIGGER
EVIDENCE --> CREATE_VIEW
EVIDENCE --> DROP_INDEX
EVIDENCE --> DROP_TABLE
EVIDENCE --> DROP_TRIGGER
EVIDENCE --> DROP_VIEW
EVIDENCE --> REINDEX
EVIDENCE --> REPLACE
EVIDENCE --> UPDATE
EVIDENCE --> IN1
EVIDENCE --> IN2
EVIDENCE --> AGGFUNC
INDEX --> BETWEEN
BETWEEN --> BETWEEN_1
BETWEEN --> BETWEEN_10
BETWEEN --> BETWEEN_100
BETWEEN_1 --> BETWEEN_1_FILE
BETWEEN_10 --> BETWEEN_10_FILES
BETWEEN_100 --> BETWEEN_100_FILE
Directory Structure Overview
Diagram: Test Corpus Directory Hierarchy
This diagram maps the physical directory structure to actual file paths in the repository. The test/evidence/ directory contains flat files named by SQL language feature, while test/index/ uses nested directories to organize tests by operator and configuration parameters.
Sources:
Evidence Tests: SQL Language Specification
Evidence tests validate that SQL statements behave according to documented specifications. These tests are stored as individual files in test/evidence/, with filenames following the pattern slt_lang_<feature>.test for language features or descriptive names for operator tests.
Diagram: Evidence Test File Organization by SQL Category
graph LR
subgraph "Evidence Test Categories"
DDL["Data Definition Language"]
DML["Data Manipulation Language"]
DQL["Data Query Language"]
end
subgraph "DDL Test Files"
DDL --> CT["slt_lang_createtrigger.test"]
DDL --> CV["slt_lang_createview.test"]
DDL --> DI["slt_lang_dropindex.test"]
DDL --> DT["slt_lang_droptable.test"]
DDL --> DTR["slt_lang_droptrigger.test"]
DDL --> DV["slt_lang_dropview.test"]
DDL --> RI["slt_lang_reindex.test"]
end
subgraph "DML Test Files"
DML --> REP["slt_lang_replace.test"]
DML --> UPD["slt_lang_update.test"]
end
subgraph "DQL Test Files"
DQL --> IN1_F["in1.test"]
DQL --> IN2_F["in2.test"]
DQL --> AGG["slt_lang_aggfunc.test"]
end
File Naming Convention
Evidence test files follow these naming patterns:
| Pattern | Description | Example |
|---|---|---|
slt_lang_<operation>.test | Tests for specific SQL language operations | slt_lang_createtrigger.test |
<operator><number>.test | Tests for SQL operators with variations | in1.test, in2.test |
slt_lang_<function_category>.test | Tests for groups of related functions | slt_lang_aggfunc.test |
The slt_lang_ prefix indicates tests that validate language specification compliance, often including references to specific evidence markers in the SQLite documentation (e.g., EVIDENCE-OF: R-10346-40046).
Sources:
Index Tests: Query Optimization Validation
Index tests are organized hierarchically to support parameterized test generation across different configurations. Unlike evidence tests, index tests use nested directories to represent test variations.
Diagram: Index Test Directory Hierarchy for BETWEEN Operator
graph TB
INDEX_ROOT["test/index/"]
subgraph "Operator Directories"
BETWEEN_DIR["between/"]
end
subgraph "Configuration Directories"
CONFIG_1["1/"]
CONFIG_10["10/"]
CONFIG_100["100/"]
end
subgraph "Test Files"
FILE_1["slt_good_0.test"]
FILES_10_0["slt_good_0.test"]
FILES_10_1["slt_good_1.test"]
FILES_10_2["slt_good_2.test"]
FILES_10_3["slt_good_3.test"]
FILES_10_4["slt_good_4.test"]
FILES_10_5["slt_good_5.test"]
FILE_100["slt_good_0.test"]
end
INDEX_ROOT --> BETWEEN_DIR
BETWEEN_DIR --> CONFIG_1
BETWEEN_DIR --> CONFIG_10
BETWEEN_DIR --> CONFIG_100
CONFIG_1 --> FILE_1
CONFIG_10 --> FILES_10_0
CONFIG_10 --> FILES_10_1
CONFIG_10 --> FILES_10_2
CONFIG_10 --> FILES_10_3
CONFIG_10 --> FILES_10_4
CONFIG_10 --> FILES_10_5
CONFIG_100 --> FILE_100
note1["Path: test/index/between/1/slt_good_0.test"]
note2["Path: test/index/between/10/slt_good_0.test"]
note3["Path: test/index/between/100/slt_good_0.test"]
CONFIG_1 -.-> note1
CONFIG_10 -.-> note2
CONFIG_100 -.-> note3
Directory Structure Pattern
Index tests follow a three-level hierarchy:
- Operator Level (
test/index/<operator>/): Top-level directory named after the SQL operator being tested - Configuration Level (
test/index/<operator>/<config>/): Numeric directories representing test configuration parameters (e.g., data volume, index cardinality) - Test Files (
test/index/<operator>/<config>/slt_good_<n>.test): Individual test files numbered sequentially
The numeric configuration directories (1, 10, 100) typically represent scaling factors or data set sizes, allowing the same test logic to be validated across different performance scenarios.
Sources: Based on repository structure analysis from high-level architecture diagrams.
Cross-Database Compatibility Markers
Test files in both evidence/ and index/ directories use conditional execution directives to control test execution based on the target database platform. These directives are embedded directly in test files:
Diagram: Conditional Execution Flow in Test Files
flowchart TD
TEST_START["Test File Execution Begins"]
READ_LINE["Read Next Line"]
CHECK_DIRECTIVE{"Line Type?"}
ONLYIF["onlyif <database>"]
SKIPIF["skipif <database>"]
HALT["halt"]
SQL["SQL Statement or Query"]
EVAL_ONLYIF{"Database\nMatches?"}
EVAL_SKIPIF{"Database\nMatches?"}
EXECUTE["Execute Statement/Query"]
SKIP["Skip Block"]
HALT_EXEC["Stop File Execution"]
TEST_START --> READ_LINE
READ_LINE --> CHECK_DIRECTIVE
CHECK_DIRECTIVE -->|Directive| ONLYIF
CHECK_DIRECTIVE -->|Directive| SKIPIF
CHECK_DIRECTIVE -->|Directive| HALT
CHECK_DIRECTIVE -->|Content| SQL
ONLYIF --> EVAL_ONLYIF
SKIPIF --> EVAL_SKIPIF
EVAL_ONLYIF -->|Yes| READ_LINE
EVAL_ONLYIF -->|No| SKIP
EVAL_SKIPIF -->|Yes| SKIP
EVAL_SKIPIF -->|No| READ_LINE
HALT --> EVAL_ONLYIF
EVAL_ONLYIF -->|Yes| HALT_EXEC
SQL --> EXECUTE
EXECUTE --> READ_LINE
SKIP --> READ_LINE
HALT_EXEC -.-> END_FILE["End of File Processing"]
READ_LINE -.->|EOF| END_FILE
Example directives from evidence tests:
- Platform exclusion: test/evidence/in1.test:4-5 demonstrates halting execution for MSSQL
- Platform-specific tests: test/evidence/in1.test:22-26 shows SQLite-only tests for empty RHS in IN operator
- SQLite extensions: test/evidence/in1.test:69-73 demonstrates table-as-operand syntax unique to SQLite
These directives enable a single corpus to serve multiple database platforms while accommodating platform-specific SQL dialects and feature sets.
Sources:
- test/evidence/in1.test:1-10
- test/evidence/in1.test:22-26
- test/evidence/in1.test:69-73
- test/evidence/slt_lang_createtrigger.test:18-19
Evidence Documentation Markers
Evidence tests include special comment markers that reference specific sections of SQLite's documentation. These markers follow the pattern EVIDENCE-OF: R-<number>-<number> and serve as bidirectional traceability links between test assertions and specification requirements:
Diagram: Evidence Marker Traceability
graph LR
subgraph "Test File Structure"
COMMENT["# Comment with EVIDENCE-OF marker"]
DIRECTIVE["Conditional execution directive"]
STATEMENT["statement ok / query directive"]
SQL_CODE["SQL code to execute"]
EXPECTED["Expected result"]
end
subgraph "SQLite Documentation"
SPEC["Specification Requirement"]
REF_ID["Reference ID: R-xxxxx-xxxxx"]
end
COMMENT -->|References| REF_ID
REF_ID -->|Documents| SPEC
SPEC -->|Validated by| STATEMENT
STATEMENT --> SQL_CODE
SQL_CODE --> EXPECTED
Example evidence markers from test files:
| Reference ID | Location | Specification Topic |
|---|---|---|
R-52275-55503 | test/evidence/in1.test:11-14 | Empty set behavior for IN operator |
R-64309-54027 | test/evidence/in1.test:16-20 | SQLite-specific empty list syntax |
R-10346-40046 | test/evidence/slt_lang_createtrigger.test:21-22 | CREATE TRIGGER statement purpose |
R-63660-13730 | test/evidence/slt_lang_createtrigger.test:65-68 | OLD/NEW reference validity rules |
These markers enable developers to trace test failures back to specific requirements and understand the intended behavior being validated.
Sources:
- test/evidence/in1.test:11-20
- test/evidence/slt_lang_createtrigger.test:21-22
- test/evidence/slt_lang_createtrigger.test:65-68
graph LR
subgraph "slt_lang Prefix Pattern"
PREFIX["slt_lang_"]
OPERATION["<operation>"]
EXTENSION[".test"]
PREFIX -->|Concatenate| OPERATION
OPERATION -->|Concatenate| EXTENSION
end
subgraph "Examples"
EX1["slt_lang_createtrigger.test"]
EX2["slt_lang_dropindex.test"]
EX3["slt_lang_reindex.test"]
end
subgraph "Operator Pattern"
OP_NAME["<operator>"]
VARIANT["<variant_number>"]
OP_EXT[".test"]
OP_NAME -->|Concatenate| VARIANT
VARIANT -->|Concatenate| OP_EXT
end
subgraph "Operator Examples"
OP1["in1.test"]
OP2["in2.test"]
end
Test File Naming and Identification
The corpus uses distinct naming patterns to support both human readability and automated processing:
Evidence Test Naming
Diagram: Evidence Test Naming Conventions
Index Test Naming
Index tests use a standardized slt_good_<n>.test naming pattern, where <n> is a zero-based sequential number. This pattern supports:
- Automated generation: Sequential numbering allows test generators to create multiple test files programmatically
- Parallel execution: Numbered files can be distributed across multiple test runners
- Configuration grouping: All files in a configuration directory share the same test parameters
The slt_good_ prefix distinguishes these as "good" (expected to pass) test cases, allowing for future expansion with slt_bad_ tests for error condition validation.
Sources: Based on repository structure analysis and naming pattern observations.
Summary
The test organization structure implements a dual taxonomy:
- Evidence tests (
test/evidence/) provide flat-file organization for SQL specification validation, with filenames directly indicating the feature under test - Index tests (
test/index/) use hierarchical directories to parameterize optimization tests across different configurations
This structure supports:
- Discoverability: Developers can locate tests by SQL feature name or operator
- Maintainability: Related tests are grouped together in predictable locations
- Cross-platform testing: Conditional directives allow platform-specific test execution
- Traceability: Evidence markers link tests to specification requirements
- Scalability: Hierarchical index tests support configuration variations without file proliferation
For details on how to execute these tests, see Usage Guide. For information on the test file format syntax, see Test File Format Specification.
Sources:
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Test Corpus Reference
Relevant source files
- test/evidence/in1.test
- test/evidence/slt_lang_aggfunc.test
- test/evidence/slt_lang_createtrigger.test
- test/index/between/1/slt_good_0.test
Purpose and Scope
This document provides a comprehensive reference for the test corpus contained in the test/ directory. The corpus consists of SQL Logic Test files organized into two primary categories: evidence tests that validate SQL language specification compliance, and index tests that verify query optimization behavior.
For information about the test file format syntax and directives, see Test File Format Specification. For guidance on running and integrating these tests, see Usage Guide.
Corpus Organization
The test corpus is organized into a two-tier directory structure based on test purpose and functionality:
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_aggfunc.test:1-500 test/evidence/slt_lang_createtrigger.test:1-220
graph TB
subgraph "test/ Directory"
ROOT["test/"]
subgraph "Evidence Tests"
EV_DIR["test/evidence/"]
EV_DDL["DDL Tests\nCREATE/DROP statements"]
EV_DML["DML Tests\nUPDATE/REPLACE"]
EV_DQL["Query Tests\nIN/NOT IN, BETWEEN"]
EV_AGG["Aggregate Functions\ncount, avg, sum, etc."]
end
subgraph "Index Tests"
IDX_DIR["test/index/"]
IDX_BETWEEN["BETWEEN Tests\nOperator optimization"]
IDX_VARIANTS["Test Variants\nbetween/1/, between/10/, etc."]
end
end
ROOT --> EV_DIR
ROOT --> IDX_DIR
EV_DIR --> EV_DDL
EV_DIR --> EV_DML
EV_DIR --> EV_DQL
EV_DIR --> EV_AGG
IDX_DIR --> IDX_BETWEEN
IDX_BETWEEN --> IDX_VARIANTS
Test Corpus Structure
The following diagram maps the logical test categories to their physical file locations and key test constructs:
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_aggfunc.test:1-500 test/evidence/slt_lang_createtrigger.test:1-220
graph LR
subgraph "Evidence Tests - DDL"
CREATE_TRIG["slt_lang_createtrigger.test\nCREATE TRIGGER statements"]
CREATE_VIEW["slt_lang_createview.test\nCREATE VIEW statements"]
DROP_INDEX["slt_lang_dropindex.test\nDROP INDEX statements"]
DROP_TABLE["slt_lang_droptable.test\nDROP TABLE statements"]
DROP_TRIG["slt_lang_droptrigger.test\nDROP TRIGGER statements"]
DROP_VIEW["slt_lang_dropview.test\nDROP VIEW statements"]
REINDEX["slt_lang_reindex.test\nREINDEX command"]
end
subgraph "Evidence Tests - DML"
REPLACE["slt_lang_replace.test\nREPLACE/INSERT OR REPLACE"]
UPDATE["slt_lang_update.test\nUPDATE statements"]
end
subgraph "Evidence Tests - Query Operators"
IN1["in1.test\nIN/NOT IN edge cases"]
IN2["in2.test\nIN/NOT IN validation"]
AGGFUNC["slt_lang_aggfunc.test\nAggregate functions"]
end
subgraph "Index Tests"
BETWEEN_DIR["index/between/"]
BETWEEN_1["between/1/slt_good_0.test"]
BETWEEN_10["between/10/slt_good_0-5.test"]
BETWEEN_100["between/100/slt_good_0.test"]
end
BETWEEN_DIR --> BETWEEN_1
BETWEEN_DIR --> BETWEEN_10
BETWEEN_DIR --> BETWEEN_100
Evidence Tests
Evidence tests validate SQL language specification compliance by testing specific behaviors documented in the SQLite specification. Each test file is organized around a particular SQL construct or operator, with inline references to the specification using EVIDENCE-OF markers.
Evidence Test File Structure
Evidence tests follow a consistent pattern with specification references and cross-database compatibility directives:
| Component | Purpose | Example |
|---|---|---|
| Specification References | Link to SQLite documentation | # EVIDENCE-OF: R-52275-55503 |
| Platform Directives | Control cross-database execution | onlyif sqlite, skipif mssql |
| Test Setup | Create tables and insert data | CREATE TABLE t1(x INTEGER) |
| Query Assertions | Validate expected results | query I nosort |
| Cleanup | Drop created objects | DROP TRIGGER t1r1 |
IN/NOT IN Operator Tests
The in1.test and in2.test files provide comprehensive coverage of IN and NOT IN operators, including edge cases with NULL values and empty sets.
Key Test Scenarios:
stateDiagram-v2
[*] --> EmptySet : Empty RHS test
[*] --> NullHandling : NULL value test
[*] --> TableOperand : Table as operand
EmptySet --> ValidateIN : SELECT 1 IN ()
EmptySet --> ValidateNOTIN : SELECT 1 NOT IN ()
NullHandling --> NullInEmpty : SELECT null IN ()
NullHandling --> NullInList : SELECT 1 IN (2,3,null)
TableOperand --> DirectTable : SELECT 1 IN t1
TableOperand --> Subquery : SELECT 1 IN (SELECT * FROM t1)
ValidateIN --> Result["Result : false (0)"]
ValidateNOTIN --> Result2["Result : true (1)"]
NullInEmpty --> Result3["Result : false (0)"]
NullInList --> Result4["Result : NULL"]
Sources: test/evidence/in1.test:11-26 test/evidence/in1.test:329-336
Example Test Structure:
From test/evidence/in1.test:11-26:
# EVIDENCE-OF: R-52275-55503 When the right operand is an empty set, the
# result of IN is false and the result of NOT IN is true, regardless of
# the left operand and even if the left operand is NULL.
onlyif sqlite # empty RHS
query I nosort
SELECT 1 IN ()
----
0
Cross-Database Compatibility:
The IN/NOT IN tests demonstrate platform-specific behavior handling:
| Platform | Empty RHS Support | Table as Operand | Notes |
|---|---|---|---|
| SQLite | Yes | Yes | Full feature support |
| MySQL | No | No | Skips empty RHS tests |
| MSSQL | No | No | Halts entire file |
| Oracle | No | No | Halts entire file |
Sources: test/evidence/in1.test:1-10 test/evidence/in1.test:22-26 test/evidence/in1.test:69-73
graph TB
subgraph "Test Setup"
TABLE["CREATE TABLE t1(x INTEGER, y VARCHAR(8))"]
DATA["INSERT VALUES: (1,'true'), (0,'false'), (NULL,'NULL')"]
end
subgraph "Aggregate Functions"
COUNT["count(x)\ncount(DISTINCT x)"]
AVG["avg(x)\navg(DISTINCT x)"]
SUM["sum(x)\nsum(DISTINCT x)"]
TOTAL["total(x)\ntotal(DISTINCT x)"]
MIN["min(x)\nmin(DISTINCT x)"]
MAX["max(x)\nmax(DISTINCT x)"]
CONCAT["group_concat(x)\ngroup_concat(x,':')"]
end
subgraph "Test Scenarios"
NULL_HANDLING["NULL value handling"]
DISTINCT["DISTINCT keyword behavior"]
TYPE_COERCION["String to number coercion"]
end
TABLE --> COUNT
TABLE --> AVG
TABLE --> SUM
TABLE --> TOTAL
TABLE --> MIN
TABLE --> MAX
TABLE --> CONCAT
COUNT --> NULL_HANDLING
AVG --> DISTINCT
SUM --> TYPE_COERCION
Aggregate Function Tests
The slt_lang_aggfunc.test file validates aggregate function behavior including count(), avg(), sum(), total(), min(), max(), and group_concat().
Aggregate Functions Test Coverage:
Sources: test/evidence/slt_lang_aggfunc.test:1-16 test/evidence/slt_lang_aggfunc.test:21-61
Key Behavioral Rules:
From test/evidence/slt_lang_aggfunc.test:24-31:
# EVIDENCE-OF: R-00466-56349 In any aggregate function that takes a
# single argument, that argument can be preceded by the keyword
# DISTINCT.
query I nosort
SELECT count(DISTINCT x) FROM t1
----
2
Aggregate Function Behavior Matrix:
| Function | NULL Handling | DISTINCT Support | Return Type | Empty Set Result |
|---|---|---|---|---|
count(x) | Excludes NULL | Yes | Integer | 0 |
avg(x) | Excludes NULL | Yes | Float | NULL |
sum(x) | Excludes NULL | Yes | Integer/Float | NULL |
total(x) | Excludes NULL | Yes | Float | 0.0 |
min(x) | Excludes NULL | Yes | Same as input | NULL |
max(x) | Excludes NULL | Yes | Same as input | NULL |
group_concat(x) | Excludes NULL | Yes | String | NULL |
Sources: test/evidence/slt_lang_aggfunc.test:83-104 test/evidence/slt_lang_aggfunc.test:198-210 test/evidence/slt_lang_aggfunc.test:372-418
graph LR
subgraph "Timing Options"
BEFORE["BEFORE"]
AFTER["AFTER"]
INSTEAD["INSTEAD OF"]
end
subgraph "Events"
INSERT["INSERT"]
UPDATE["UPDATE"]
DELETE["DELETE"]
end
subgraph "Trigger Examples"
T1["t1r1: UPDATE ON t1"]
T2["t1r2: DELETE ON t1"]
T3["t1r3: INSERT ON t1"]
T5["t1r5: AFTER DELETE"]
T6["t1r6: AFTER INSERT"]
T7["t1r7: AFTER UPDATE"]
T8["t1r8: BEFORE DELETE"]
T9["t1r9: BEFORE INSERT"]
T10["t1r10: BEFORE UPDATE"]
end
BEFORE --> DELETE
BEFORE --> INSERT
BEFORE --> UPDATE
AFTER --> DELETE
AFTER --> INSERT
AFTER --> UPDATE
DDL Tests - CREATE TRIGGER
The slt_lang_createtrigger.test file validates trigger creation and behavior across different timing options and events.
Trigger Timing and Event Matrix:
Sources: test/evidence/slt_lang_createtrigger.test:21-96
Example Test Pattern:
From test/evidence/slt_lang_createtrigger.test:21-29:
# EVIDENCE-OF: R-10346-40046 The CREATE TRIGGER statement is used to add
# triggers to the database schema.
statement ok
CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END;
# already exists
statement error
CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END;
Platform Support:
From test/evidence/slt_lang_createtrigger.test:18-19:
onlyif mssql
halt
The CREATE TRIGGER tests are skipped on MSSQL due to syntax differences in trigger creation.
Sources: test/evidence/slt_lang_createtrigger.test:18-19 test/evidence/slt_lang_createtrigger.test:75-96
Index and Optimization Tests
Index tests focus on query optimization behavior, particularly how the query planner handles indexed columns with various operators. These tests are organized by operator type and data characteristics.
BETWEEN Operator Tests
The test/index/between/ directory contains test variants that exercise BETWEEN operator optimization with different data distributions and index configurations.
Test Variant Organization:
| Directory | Description | File Count | Purpose |
|---|---|---|---|
between/1/ | Single-row BETWEEN tests | 1 file | Minimal data scenarios |
between/10/ | Small dataset BETWEEN | 6 files | Basic optimization paths |
between/100/ | Medium dataset BETWEEN | 1 file | Typical query patterns |
between/1000/ | Large dataset BETWEEN | Multiple | Complex optimization |
These variants test how SQLite optimizes BETWEEN queries across different data scales and index configurations.
Sources: Based on system architecture diagrams and corpus organization patterns
Test Directive Usage Patterns
All test files use a consistent set of directives to control execution and validate results:
Common Directives:
| Directive | Purpose | Example Usage |
|---|---|---|
statement ok | Execute SQL expecting success | statement ok |
CREATE TABLE t1(x INTEGER) | ||
statement error | Execute SQL expecting failure | statement error |
CREATE TRIGGER t1r1 ... | ||
query I nosort | Execute query returning integer | query I nosort |
SELECT count(*) FROM t1 | ||
query T nosort | Execute query returning text | query T nosort |
SELECT group_concat(x) FROM t1 | ||
query R nosort | Execute query returning real/float | query R nosort |
SELECT avg(x) FROM t1 | ||
onlyif <db> | Run only on specified database | onlyif sqlite |
skipif <db> | Skip on specified database | skipif mysql |
halt | Stop processing file | halt |
hash-threshold N | Set result hashing threshold | hash-threshold 8 |
Sources: test/evidence/in1.test:4-9 test/evidence/slt_lang_aggfunc.test1 test/evidence/slt_lang_createtrigger.test:18-19
Label-Based Result Validation
Test files use labels to group related assertions that should produce identical results:
Label Pattern:
From test/evidence/in1.test:69-78:
onlyif sqlite
query I nosort label-1
SELECT 1 IN t1
----
0
query I nosort label-1
SELECT 1 IN (SELECT * FROM t1)
----
0
This pattern validates that SELECT 1 IN t1 (SQLite-specific syntax) produces the same result as SELECT 1 IN (SELECT * FROM t1) (standard SQL syntax).
Sources: test/evidence/in1.test:69-111 test/evidence/slt_lang_aggfunc.test:187-196
flowchart TD
START["Test File Start"] --> CHECK_PLATFORM{"Platform-specific\nhalt directives"}
CHECK_PLATFORM -->|onlyif mssql halt| HALT_MSSQL["Skip entire file\nfor MSSQL"]
CHECK_PLATFORM -->|onlyif oracle halt| HALT_ORACLE["Skip entire file\nfor Oracle"]
CHECK_PLATFORM --> CONTINUE["Continue processing"]
CONTINUE --> TEST_BLOCK["Test Block"]
TEST_BLOCK --> SQLITE_ONLY{"onlyif sqlite"}
TEST_BLOCK --> SKIP_MYSQL{"skipif mysql"}
SQLITE_ONLY -->|Yes| SQLITE_TEST["Execute SQLite-specific test"]
SQLITE_ONLY -->|No| PORTABLE["Execute portable test"]
SKIP_MYSQL -->|Match| SKIP["Skip test block"]
SKIP_MYSQL -->|No match| EXECUTE["Execute test"]
SQLITE_TEST --> NEXT["Next test"]
PORTABLE --> NEXT
SKIP --> NEXT
EXECUTE --> NEXT
HALT_MSSQL --> END["End of file"]
HALT_ORACLE --> END
NEXT --> END
Cross-Database Compatibility Patterns
The corpus demonstrates sophisticated cross-database testing using conditional execution directives:
Sources: test/evidence/in1.test:1-10 test/evidence/in1.test:22-26 test/evidence/in1.test:69-73
Test Corpus Statistics
File Distribution:
| Category | Example Files | Test Focus |
|---|---|---|
| DDL Tests | slt_lang_createtrigger.test, slt_lang_createview.test, slt_lang_dropindex.test | Object creation/deletion |
| DML Tests | slt_lang_update.test, slt_lang_replace.test | Data manipulation |
| Query Operators | in1.test, in2.test | IN/NOT IN edge cases |
| Aggregate Functions | slt_lang_aggfunc.test | COUNT, AVG, SUM, MIN, MAX, etc. |
| Index Optimization | index/between/*/slt_good_*.test | Query planner behavior |
Evidence Reference Format:
All evidence tests include specification references in the format:
# EVIDENCE-OF: R-XXXXX-XXXXX <description>
For example, from test/evidence/in1.test:11-13:
# EVIDENCE-OF: R-52275-55503 When the right operand is an empty set, the
# result of IN is false and the result of NOT IN is true, regardless of
# the left operand and even if the left operand is NULL.
These references link directly to the SQLite specification document.
Sources: test/evidence/in1.test:11-20 test/evidence/slt_lang_aggfunc.test:24-26 test/evidence/slt_lang_createtrigger.test:21-22
Summary
The test corpus provides comprehensive coverage of SQL language features through two complementary categories:
- Evidence Tests (
test/evidence/): Validate SQL specification compliance across DDL, DML, and query operations with explicit specification references - Index Tests (
test/index/): Verify query optimization behavior across different data scales and index configurations
For detailed information about specific test categories, see:
- SQL Language Evidence Tests: Section 3.1
- Index and Optimization Tests: Section 3.2
- Test file format specification: Section 6
- Running and integrating tests: Section 5
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_aggfunc.test:1-500 test/evidence/slt_lang_createtrigger.test:1-220
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Data Definition Language (DDL) Tests
Relevant source files
- test/evidence/slt_lang_createtrigger.test
- test/evidence/slt_lang_createview.test
- test/evidence/slt_lang_dropindex.test
- test/evidence/slt_lang_droptable.test
- test/evidence/slt_lang_droptrigger.test
- test/evidence/slt_lang_dropview.test
Purpose and Scope
This page documents the Data Definition Language (DDL) test suite within the SQL Language Evidence Tests category. These tests validate the correctness of CREATE and DROP statements for database objects including tables, views, indexes, and triggers. The tests verify compliance with SQL language specifications and SQLite-specific DDL behaviors.
For detailed coverage of individual CREATE statement types, see CREATE Statement Tests. For detailed coverage of individual DROP statement types, see DROP Statement Tests. For data manipulation tests, see Data Manipulation Language (DML) Tests-tests).
Overview
DDL tests verify the schema manipulation capabilities of SQL implementations. These tests ensure that database objects can be created, exist with the correct properties, and can be removed from the schema. Each test file focuses on a specific DDL command and validates both successful operations and expected error conditions.
The test suite follows a consistent pattern:
- Setup : Create a base table with sample data
- Operation : Execute the DDL statement being tested
- Verification : Confirm the operation succeeded or failed as expected
- Cleanup : Remove created objects
Test File Locations
All DDL test files are located in the test/evidence/ directory:
| Test File | DDL Command | Purpose |
|---|---|---|
slt_lang_createtrigger.test | CREATE TRIGGER | Validates trigger creation with various timing and event combinations |
slt_lang_createview.test | CREATE VIEW | Validates view creation including temporary views and schema qualification |
slt_lang_dropindex.test | DROP INDEX | Validates index removal and error handling for non-existent indexes |
slt_lang_droptable.test | DROP TABLE | Validates table removal and cascading deletion of dependent objects |
slt_lang_droptrigger.test | DROP TRIGGER | Validates trigger removal and automatic cleanup when tables are dropped |
slt_lang_dropview.test | DROP VIEW | Validates view removal without affecting underlying base tables |
Sources: test/evidence/slt_lang_createtrigger.test:1-220 test/evidence/slt_lang_createview.test:1-129 test/evidence/slt_lang_dropindex.test:1-35 test/evidence/slt_lang_droptable.test:1-56 test/evidence/slt_lang_droptrigger.test:1-54 test/evidence/slt_lang_dropview.test:1-60
Test File Structure and Evidence Mapping
Evidence-Based Testing Model
Diagram: Evidence-Based Test Validation Model
Each test file contains inline comments that reference specific evidence identifiers from SQLite's documentation. These identifiers (format: R-XXXXX-XXXXX) trace each test back to the precise specification it validates.
Sources: test/evidence/slt_lang_createtrigger.test:21-22 test/evidence/slt_lang_createview.test:19-20 test/evidence/slt_lang_dropindex.test:19-20 test/evidence/slt_lang_droptable.test:19-20 test/evidence/slt_lang_droptrigger.test:21-22 test/evidence/slt_lang_dropview.test:19-20
DDL Test Coverage Matrix
Database Object Lifecycle Testing
Diagram: Database Object State Transitions and Test Coverage
Sources: test/evidence/slt_lang_createtrigger.test:25-29 test/evidence/slt_lang_createview.test:23-28 test/evidence/slt_lang_droptrigger.test:28-36 test/evidence/slt_lang_dropview.test:31-39
DDL Command Test Matrix
| DDL Command | Success Case | Duplicate Error | Drop Missing Error | Cascading Effects | Test File Lines |
|---|---|---|---|---|---|
CREATE TRIGGER | ✓ Line 25 | ✓ Line 29 | N/A | Dropped with table | slt_lang_createtrigger.test:25-219 |
CREATE VIEW | ✓ Line 23 | ✓ Line 27 | N/A | Independent lifecycle | slt_lang_createview.test:23-120 |
DROP INDEX | ✓ Line 24 | N/A | ✓ Line 34 | Removed with table | slt_lang_dropindex.test:24-34 |
DROP TABLE | ✓ Line 23 | N/A | ✓ Line 31 | Removes indexes/triggers | slt_lang_droptable.test:23-41 |
DROP TRIGGER | ✓ Line 28 | N/A | ✓ Line 36 | Auto-dropped with table | slt_lang_droptrigger.test:28-52 |
DROP VIEW | ✓ Line 31 | N/A | ✓ Line 39 | Base tables unchanged | slt_lang_dropview.test:31-59 |
Sources: All six test files in test/evidence/
Common Test Setup Pattern
All DDL test files share a consistent initialization sequence that establishes a baseline table for testing:
CREATE TABLE t1( x INTEGER, y VARCHAR(8) )
INSERT INTO t1 VALUES(1,'true')
INSERT INTO t1 VALUES(0,'false')
INSERT INTO t1 VALUES(NULL,'NULL')
CREATE INDEX t1i1 ON t1(x)
This pattern appears at slt_lang_createtrigger.test:4-16 slt_lang_createview.test:4-16 slt_lang_dropindex.test:4-16 slt_lang_droptable.test:4-16 slt_lang_droptrigger.test:4-16 and slt_lang_dropview.test:4-16
The setup provides:
- A table (
t1) with integer and varchar columns - Sample data including
NULLvalues for comprehensive testing - An index (
t1i1) for testing cascading deletion behavior
Cross-Database Compatibility Handling
Platform-Specific Test Execution
Diagram: Cross-Database Test Execution Control Flow
Sources: test/evidence/slt_lang_createtrigger.test:18-19 test/evidence/slt_lang_dropindex.test:22-24 test/evidence/slt_lang_createview.test:47-53
Platform-Specific Behavior Differences
| Database Object | SQLite Behavior | MSSQL Behavior | Test File Reference |
|---|---|---|---|
| Triggers | Fully supported | Tests halted | slt_lang_createtrigger.test:18-19 |
| Temporary Views | CREATE TEMP VIEW supported | Not tested (SQLite-only) | slt_lang_createview.test:47-53 |
| View Mutations | DELETE/UPDATE on views forbidden | DELETE/UPDATE on views allowed | slt_lang_createview.test:68-85 |
| DROP INDEX | Standard syntax | Different syntax (skipped) | slt_lang_dropindex.test:22-34 |
| IF EXISTS Clause | Supported on DROP TABLE | Not tested (skipped) | slt_lang_droptable.test:49-55 |
The test files use three primary conditional directives:
onlyif <database>: Execute statement only on specified databaseskipif <database>: Skip statement on specified databasehalt: Stop test execution immediately (used when entire test file is incompatible)
Sources: test/evidence/slt_lang_createtrigger.test:18-19 test/evidence/slt_lang_createview.test:47-85 test/evidence/slt_lang_dropindex.test:22-34 test/evidence/slt_lang_droptable.test:49-55
Cascading Deletion and Object Dependencies
Dependency Relationships
Diagram: Object Dependency and Cascading Deletion Behavior
Cascading Deletion Tests
Table Removal Cascades :
- When a table is dropped, all associated indexes are automatically removed (slt_lang_droptable.test:36-41)
- When a table is dropped, all associated triggers are automatically removed (slt_lang_droptrigger.test:42-52)
View Removal Independence :
- When a view is dropped, underlying base tables are not modified (slt_lang_dropview.test:44-59)
- Base table data remains accessible after view deletion (slt_lang_dropview.test:56-59)
Test Verification Examples :
-
Index cascade verification at slt_lang_droptable.test:39-41:
# this should error, as was dropped with table statement error DROP INDEX t1i1; -
Trigger cascade verification at slt_lang_droptrigger.test:44-52:
statement ok CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END; statement ok DROP TABLE t1 # already deleted when table dropped statement error DROP TRIGGER t1r1 -
View independence verification at slt_lang_dropview.test:48-59:
query I rowsort label-0 SELECT x FROM view2 ---- 0 statement ok DROP VIEW view2 query I rowsort label-0 SELECT x FROM t1 WHERE x=0 ---- 0
Sources: test/evidence/slt_lang_droptable.test:36-41 test/evidence/slt_lang_droptrigger.test:42-52 test/evidence/slt_lang_dropview.test:44-59
Trigger Timing and Event Combinations
The CREATE TRIGGER test validates all combinations of trigger timing (BEFORE/AFTER) and trigger events (INSERT/UPDATE/DELETE):
| Trigger Name | Timing | Event | Test Line |
|---|---|---|---|
t1r1 | (default) | UPDATE | [Line 25](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 25) |
t1r2 | (default) | DELETE | [Line 40](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 40) |
t1r3 | (default) | INSERT | [Line 43](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 43) |
t1r4 | (default) | UPDATE | [Line 46](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 46) |
t1r5 | AFTER | DELETE | [Line 81](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 81) |
t1r6 | AFTER | INSERT | [Line 84](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 84) |
t1r7 | AFTER | UPDATE | [Line 87](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 87) |
t1r8 | BEFORE | DELETE | [Line 90](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 90) |
t1r9 | BEFORE | INSERT | [Line 93](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 93) |
t1r10 | BEFORE | UPDATE | [Line 96](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 96) |
All triggers are subsequently dropped at slt_lang_createtrigger.test:191-219 to verify cleanup functionality.
Sources: test/evidence/slt_lang_createtrigger.test:25-96 test/evidence/slt_lang_createtrigger.test:191-219
View Mutability Testing
Read-Only Enforcement
Views in SQLite are read-only, meaning INSERT, UPDATE, and DELETE operations are not permitted. The test file validates this behavior at slt_lang_createview.test:65-103:
SQLite-specific read-only enforcement :
onlyif sqlite
statement error
DELETE FROM view1 WHERE x>0
onlyif sqlite
statement error
INSERT INTO view1 VALUES(2,'unknown')
onlyif sqlite
statement error
UPDATE view1 SET x=2
MSSQL exception : MSSQL allows certain DELETE and UPDATE operations on views (slt_lang_createview.test:68-85), demonstrating platform-specific SQL implementation differences.
Sources: test/evidence/slt_lang_createview.test:65-103
Evidence Reference System
Evidence Identifier Format
Test files contain evidence comments that link test cases to specific sections of SQLite's documentation. The format follows the pattern:
# EVIDENCE-OF: R-XXXXX-XXXXX <description>
# TBD-EVIDENCE-OF: R-XXXXX-XXXXX <description>
# PARTIAL-EVIDENCE-OF: R-XXXXX-XXXXX <description>
Where:
EVIDENCE-OF: Fully implemented and testedTBD-EVIDENCE-OF: Evidence identified but test not yet implementedPARTIAL-EVIDENCE-OF: Partially tested (some aspects covered)
Key Evidence Points Tested
| Evidence ID | Description | Test File |
|---|---|---|
R-10346-40046 | CREATE TRIGGER adds triggers to schema | slt_lang_createtrigger.test:21-25 |
R-13439-14752 | CREATE VIEW assigns name to SELECT statement | slt_lang_createview.test:19-23 |
R-42037-15614 | DROP INDEX removes index from schema | slt_lang_dropindex.test:19-24 |
R-01463-03846 | DROP TABLE removes table from schema | slt_lang_droptable.test:19-23 |
R-61172-15671 | DROP TRIGGER removes trigger from schema | slt_lang_droptrigger.test:21-28 |
R-27002-52307 | DROP VIEW removes view from schema | slt_lang_dropview.test:19-31 |
R-33950-57093 | Indices and triggers deleted with table | slt_lang_droptable.test:36-41 |
R-37808-62273 | Triggers auto-dropped when table dropped | slt_lang_droptrigger.test:41-52 |
R-16775-34716 | Cannot DELETE/INSERT/UPDATE a view | slt_lang_createview.test:65-103 |
R-00359-41639 | View DROP doesn't modify base tables | slt_lang_dropview.test:44-59 |
Sources: All evidence comments across the six DDL test files
Test Execution Workflow
Standard Test Sequence
Each DDL test file follows this execution pattern:
- Hash threshold declaration :
hash-threshold 8([Line 1 in all files](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Line 1 in all files)) - Base table creation : Standard
t1table setup ([Lines 3-16](https://github.com/jzombie/sqlite-sqllogictest-corpus/blob/8a29188b/Lines 3-16)) - Primary DDL operation : Execute the CREATE or DROP statement being tested
- Duplicate/missing error validation : Verify appropriate errors for invalid operations
- Cascading behavior validation : Confirm dependent objects are handled correctly
- Cleanup : Remove all created objects
Error Validation Pattern
Tests verify both successful operations and expected failures:
Success validation :
statement ok
CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END;
Duplicate creation error :
# already exists
statement error
CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END;
Missing object error :
# never existed
statement error
DROP TRIGGER tXrX
This pattern appears consistently across all DDL test files, establishing a comprehensive validation matrix for each DDL command.
Sources: test/evidence/slt_lang_createtrigger.test:25-29 test/evidence/slt_lang_droptrigger.test:28-36
Related Test Categories
For comprehensive coverage of SQL testing:
- CREATE statement details : See CREATE Statement Tests for in-depth coverage of
CREATE TRIGGERandCREATE VIEWsyntax variations - DROP statement details : See DROP Statement Tests for in-depth coverage of all
DROPstatement types - DML operations : See Data Manipulation Language (DML) Tests-tests) for
INSERT,UPDATE,DELETE, andREPLACEtesting - Query operations : See Query Operator Tests for
SELECTstatement validation - Test format specification : See Test File Format Specification for complete DSL syntax reference
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
CREATE Statement Tests
Relevant source files
Purpose and Scope
This page documents the CREATE statement tests in the SQL Logic Test corpus, specifically covering CREATE TRIGGER and CREATE VIEW statements. These tests validate the behavior of database object creation commands according to SQL specifications and SQLite-specific extensions.
For tests covering DROP statements, see DROP Statement Tests. For broader DDL test coverage, see Data Definition Language (DDL) Tests-tests).
Test File Overview
The CREATE statement tests are organized into two primary test files located in the test/evidence/ directory:
| Test File | Primary Focus | Lines of Code |
|---|---|---|
slt_lang_createtrigger.test | CREATE TRIGGER statement validation | 220 |
slt_lang_createview.test | CREATE VIEW statement validation | 129 |
Both test files follow the SQL Logic Test format and include evidence references to SQLite documentation sections, cross-database compatibility directives, and comprehensive behavioral validation.
Sources: test/evidence/slt_lang_createtrigger.test:1-220 test/evidence/slt_lang_createview.test:1-129
Test Execution Model
Diagram: Test Execution Flow for CREATE Statement Tests
This state machine illustrates the standard test pattern used across both slt_lang_createtrigger.test and slt_lang_createview.test. Each test file establishes a base table (t1), populates test data, and then executes CREATE statements with validation of both success and error conditions.
Sources: test/evidence/slt_lang_createtrigger.test:3-16 test/evidence/slt_lang_createview.test:3-16 test/evidence/slt_lang_createtrigger.test:18-19
CREATE TRIGGER Tests
Trigger Timing and Event Matrix
The slt_lang_createtrigger.test file validates all combinations of trigger timing (BEFORE/AFTER) and trigger events (INSERT/UPDATE/DELETE):
Diagram: Trigger Test Coverage Matrix
graph LR
subgraph "Trigger Events"
INSERT["INSERT"]
UPDATE["UPDATE"]
DELETE["DELETE"]
end
subgraph "Trigger Timing"
BEFORE["BEFORE"]
AFTER["AFTER"]
NONE["(no timing)"]
end
subgraph "Test Coverage"
t1r1["t1r1: UPDATE (no timing)"]
t1r2["t1r2: DELETE (no timing)"]
t1r3["t1r3: INSERT (no timing)"]
t1r4["t1r4: UPDATE (no timing)"]
t1r5["t1r5: AFTER DELETE"]
t1r6["t1r6: AFTER INSERT"]
t1r7["t1r7: AFTER UPDATE"]
t1r8["t1r8: BEFORE DELETE"]
t1r9["t1r9: BEFORE INSERT"]
t1r10["t1r10: BEFORE UPDATE"]
end
UPDATE --> t1r1
DELETE --> t1r2
INSERT --> t1r3
UPDATE --> t1r4
AFTER --> t1r5
AFTER --> t1r6
AFTER --> t1r7
BEFORE --> t1r8
BEFORE --> t1r9
BEFORE --> t1r10
DELETE --> t1r5
INSERT --> t1r6
UPDATE --> t1r7
DELETE --> t1r8
INSERT --> t1r9
UPDATE --> t1r10
This diagram maps the 10 trigger test cases (t1r1 through t1r10) to their respective timing and event combinations, demonstrating comprehensive coverage of the CREATE TRIGGER syntax.
Sources: test/evidence/slt_lang_createtrigger.test:25-96
Basic Trigger Creation Syntax
The test validates basic CREATE TRIGGER syntax with the following pattern:
CREATE TRIGGER trigger_name [BEFORE|AFTER] event ON table_name
BEGIN
sql_statements;
END;
Key test cases include:
| Test Case | Trigger Name | Timing | Event | Line Reference |
|---|---|---|---|---|
| Basic UPDATE | t1r1 | None | UPDATE | test/evidence/slt_lang_createtrigger.test25 |
| Basic DELETE | t1r2 | None | DELETE | test/evidence/slt_lang_createtrigger.test40 |
| Basic INSERT | t1r3 | None | INSERT | test/evidence/slt_lang_createtrigger.test43 |
| AFTER DELETE | t1r5 | AFTER | DELETE | test/evidence/slt_lang_createtrigger.test81 |
| BEFORE INSERT | t1r9 | BEFORE | INSERT | test/evidence/slt_lang_createtrigger.test93 |
Sources: test/evidence/slt_lang_createtrigger.test:25-96
Duplicate Trigger Validation
The test file validates that creating a trigger with an existing name produces an error:
statement ok
CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END;
# already exists
statement error
CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END;
This ensures enforcement of unique trigger names within the database schema.
Sources: test/evidence/slt_lang_createtrigger.test:24-29
OLD and NEW Reference Validation
Diagram: OLD and NEW Reference Validity Matrix
The test validates evidence reference R-63660-13730 which specifies: "OLD and NEW references may only be used in triggers on events for which they are relevant, as follows: INSERT NEW references are valid UPDATE NEW and OLD references are valid DELETE OLD references are valid"
Sources: test/evidence/slt_lang_createtrigger.test:65-68
Evidence References
The slt_lang_createtrigger.test file contains extensive evidence references linking test cases to specific sections of SQLite documentation:
| Evidence ID | Description | Line Reference |
|---|---|---|
| R-10346-40046 | CREATE TRIGGER adds triggers to schema | test/evidence/slt_lang_createtrigger.test:21-22 |
| R-63660-13730 | OLD/NEW reference validity rules | test/evidence/slt_lang_createtrigger.test:65-68 |
| R-45175-37688 | WHEN clause conditional execution | test/evidence/slt_lang_createtrigger.test:70-71 |
| R-12597-09253 | No WHEN clause executes every time | test/evidence/slt_lang_createtrigger.test:73-74 |
| R-35362-38850 | BEFORE/AFTER timing semantics | test/evidence/slt_lang_createtrigger.test:76-78 |
The evidence references follow the format R-XXXXX-XXXXX and correspond to specific requirements in the SQLite documentation.
Sources: test/evidence/slt_lang_createtrigger.test:21-190
Platform Compatibility Directive
The test includes an early halt for Microsoft SQL Server:
onlyif mssql
halt
This directive causes the test to stop execution on MSSQL platforms at line 19, indicating that MSSQL has significant differences in trigger implementation that make the remaining tests incompatible.
Sources: test/evidence/slt_lang_createtrigger.test:18-19
Trigger Cleanup
All created triggers are explicitly dropped at the end of the test:
statement ok
DROP TRIGGER t1r1
...
statement ok
DROP TRIGGER t1r10
This cleanup ensures test isolation and verifies that the DROP TRIGGER statement functions correctly.
Sources: test/evidence/slt_lang_createtrigger.test:192-219
CREATE VIEW Tests
Basic View Creation
Diagram: View Creation and Query Equivalence
The test validates that a view acts as a "pre-packaged SELECT statement" by demonstrating query equivalence between direct table queries and view queries. Both SELECT x FROM t1 WHERE x>0 and SELECT x FROM view1 produce identical results.
Sources: test/evidence/slt_lang_createview.test:22-37
View Creation Test Cases
| Test Case | View Name | Type | Line Reference |
|---|---|---|---|
| Basic view | view1 | Permanent | test/evidence/slt_lang_createview.test23 |
| Duplicate detection | view1 | Error case | test/evidence/slt_lang_createview.test:26-27 |
| Temporary view (TEMP) | view2 | SQLite-specific | test/evidence/slt_lang_createview.test:48-49 |
| Temporary view (TEMPORARY) | view3 | SQLite-specific | test/evidence/slt_lang_createview.test:52-53 |
Sources: test/evidence/slt_lang_createview.test:22-53
Temporary View Creation (SQLite-Specific)
The test validates SQLite-specific TEMP and TEMPORARY view syntax:
onlyif sqlite
statement ok
CREATE TEMP VIEW view2 AS SELECT x FROM t1 WHERE x>0
onlyif sqlite
statement ok
CREATE TEMPORARY VIEW view3 AS SELECT x FROM t1 WHERE x>0
These tests are guarded by the onlyif sqlite directive, as temporary views are a SQLite-specific feature. Evidence reference R-48816-31606 specifies that temporary views are "only visible to the process that opened the database and is automatically deleted when the database is closed."
Sources: test/evidence/slt_lang_createview.test:42-53
Read-Only View Constraints
Diagram: Platform-Specific View Modification Behavior
The test validates evidence reference R-16775-34716: "You cannot DELETE, INSERT, or UPDATE a view" and R-05363-17893: "Views are read-only in SQLite."
However, the test demonstrates platform-specific behavior using conditional directives:
SQLite behavior (read-only):
onlyif sqlite
statement error
DELETE FROM view1 WHERE x>0
onlyif sqlite
statement error
UPDATE view1 SET x=2
MSSQL behavior (allows UPDATE/DELETE):
onlyif mssql # this is allowed
statement ok
DELETE FROM view1 WHERE x>0
onlyif mssql # this is allowed
statement ok
UPDATE view1 SET x=2
Universal behavior (INSERT always fails):
statement error
INSERT INTO view1 VALUES(2,'unknown')
Sources: test/evidence/slt_lang_createview.test:65-103
View Modification Operations Test Matrix
| Operation | SQLite | MSSQL | Test Lines |
|---|---|---|---|
| DELETE | Error (skipif mssql) | OK (onlyif mssql) | test/evidence/slt_lang_createview.test:68-74 |
| INSERT | Error (universal) | Error (universal) | test/evidence/slt_lang_createview.test:76-77 |
| UPDATE | Error (skipif mssql) | OK (onlyif mssql) | test/evidence/slt_lang_createview.test:79-85 |
| INSERT OR REPLACE | Error (onlyif sqlite) | N/A | test/evidence/slt_lang_createview.test:98-99 |
Sources: test/evidence/slt_lang_createview.test:65-103
View Cleanup and Error Validation
Diagram: View Cleanup and Error Condition Validation
The test validates proper error handling for DROP VIEW operations:
- Successfully dropping existing views
- Error when dropping already-dropped views
- Error when dropping non-existent views
Evidence reference R-10484-47921 states: "Views are removed with the DROP VIEW command."
Sources: test/evidence/slt_lang_createview.test:111-128
Cross-Database Compatibility
Platform-Specific Directives Summary
| Directive | Database | Location | Purpose |
|---|---|---|---|
onlyif mssql + halt | MSSQL | test/evidence/slt_lang_createtrigger.test:18-19 | Halt trigger tests for MSSQL |
onlyif sqlite | SQLite | test/evidence/slt_lang_createview.test:47-53 | TEMP view syntax |
skipif mssql | MSSQL | test/evidence/slt_lang_createview.test:68-69 | Skip DELETE error test |
onlyif mssql | MSSQL | test/evidence/slt_lang_createview.test:72-74 | Allow DELETE on views |
onlyif sqlite | SQLite | test/evidence/slt_lang_createview.test:89-103 | Validate read-only enforcement |
These directives enable the same test file to validate both standard SQL behavior and platform-specific deviations. For comprehensive information on conditional execution, see Conditional Execution Directives.
Sources: test/evidence/slt_lang_createtrigger.test:18-19 test/evidence/slt_lang_createview.test:47-103
Known Platform Differences
Diagram: Platform-Specific CREATE Statement Behavior
For detailed platform behavior differences, see Platform-Specific Behaviors.
Sources: test/evidence/slt_lang_createtrigger.test:18-19 test/evidence/slt_lang_createview.test:68-103
Test Setup Pattern
Both test files follow a consistent setup pattern:
- Hash threshold configuration :
hash-threshold 8enables result hashing for large result sets - Base table creation :
CREATE TABLE t1( x INTEGER, y VARCHAR(8) ) - Test data insertion : Three rows representing true, false, and NULL values
- Index creation :
CREATE INDEX t1i1 ON t1(x)for query optimization testing
This standardized setup provides a consistent foundation for testing CREATE statements across different scenarios.
Sources: test/evidence/slt_lang_createtrigger.test:1-16 test/evidence/slt_lang_createview.test:1-16
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
DROP Statement Tests
Relevant source files
- test/evidence/slt_lang_dropindex.test
- test/evidence/slt_lang_droptable.test
- test/evidence/slt_lang_droptrigger.test
- test/evidence/slt_lang_dropview.test
Purpose and Scope
This document details the DROP statement tests within the SQL Logic Test corpus, which validate the behavior of SQL DROP commands for removing database objects. These tests are located in the test/evidence/ directory and cover four primary DROP statement types: DROP INDEX, DROP TABLE, DROP TRIGGER, and DROP VIEW.
The tests verify SQL specification compliance through evidence markers that reference specific requirements from SQL language documentation. Each test file follows a consistent pattern of creating objects, dropping them successfully, and validating error conditions when attempting to drop non-existent or already-dropped objects.
For information about CREATE statement tests, see CREATE Statement Tests. For broader DDL test coverage, see Data Definition Language (DDL) Tests-tests).
Test File Organization
The DROP statement tests are organized into four evidence test files, each targeting a specific database object type:
Sources: test/evidence/slt_lang_dropindex.test:1-35 test/evidence/slt_lang_droptable.test:1-56 test/evidence/slt_lang_droptrigger.test:1-53 test/evidence/slt_lang_dropview.test:1-60
graph TB
DROP_ROOT["DROP Statement Tests\ntest/evidence/"]
DROP_INDEX["slt_lang_dropindex.test\nDROP INDEX validation"]
DROP_TABLE["slt_lang_droptable.test\nDROP TABLE validation"]
DROP_TRIGGER["slt_lang_droptrigger.test\nDROP TRIGGER validation"]
DROP_VIEW["slt_lang_dropview.test\nDROP VIEW validation"]
DROP_ROOT --> DROP_INDEX
DROP_ROOT --> DROP_TABLE
DROP_ROOT --> DROP_TRIGGER
DROP_ROOT --> DROP_VIEW
DROP_INDEX --> IDX_EV["Evidence: R-42037-15614\nIndex removal behavior"]
DROP_TABLE --> TBL_EV1["Evidence: R-01463-03846\nTable removal behavior"]
DROP_TABLE --> TBL_EV2["Evidence: R-33950-57093\nCascading deletion"]
DROP_TABLE --> TBL_EV3["Evidence: R-57089-01510\nIF EXISTS clause"]
DROP_TRIGGER --> TRG_EV1["Evidence: R-61172-15671\nTrigger removal behavior"]
DROP_TRIGGER --> TRG_EV2["Evidence: R-37808-62273\nAuto-drop with table"]
DROP_VIEW --> VW_EV1["Evidence: R-27002-52307\nView removal behavior"]
DROP_VIEW --> VW_EV2["Evidence: R-00359-41639\nData preservation"]
Evidence Markers
Each test file contains evidence markers that reference specific requirements from SQL language specifications:
| Evidence ID | Description | Test File |
|---|---|---|
| R-42037-15614 | DROP INDEX removes an index added with CREATE INDEX | slt_lang_dropindex.test |
| R-01463-03846 | DROP TABLE removes a table added with CREATE TABLE | slt_lang_droptable.test |
| R-33950-57093 | All indices and triggers deleted when table dropped | slt_lang_droptable.test |
| R-57089-01510 | IF EXISTS clause suppresses error for non-existent table | slt_lang_droptable.test |
| R-61172-15671 | DROP TRIGGER removes a trigger created by CREATE TRIGGER | slt_lang_droptrigger.test |
| R-37808-62273 | Triggers automatically dropped when table is dropped | slt_lang_droptrigger.test |
| R-27002-52307 | DROP VIEW removes a view created by CREATE VIEW | slt_lang_dropview.test |
| R-18673-21346 | View resolution using standard object resolution | slt_lang_dropview.test |
| R-00359-41639 | View removal does not modify underlying base tables | slt_lang_dropview.test |
Sources: test/evidence/slt_lang_dropindex.test:19-20 test/evidence/slt_lang_droptable.test:19-20 test/evidence/slt_lang_droptable.test:36-37 test/evidence/slt_lang_droptable.test:43-44 test/evidence/slt_lang_droptrigger.test:21-22 test/evidence/slt_lang_droptrigger.test:41-42 test/evidence/slt_lang_dropview.test:19-20 test/evidence/slt_lang_dropview.test:44-46
Common Test Pattern
All DROP statement tests follow a consistent execution pattern:
Sources: test/evidence/slt_lang_dropindex.test:3-34 test/evidence/slt_lang_droptable.test:3-31 test/evidence/slt_lang_droptrigger.test:3-36 test/evidence/slt_lang_dropview.test:3-39
Standard Test Setup
Each DROP statement test file begins with identical setup code:
hash-threshold 8
statement ok
CREATE TABLE t1( x INTEGER, y VARCHAR(8) )
statement ok
INSERT INTO t1 VALUES(1,'true')
statement ok
INSERT INTO t1 VALUES(0,'false')
statement ok
INSERT INTO t1 VALUES(NULL,'NULL')
statement ok
CREATE INDEX t1i1 ON t1(x)
This creates a baseline table t1 with three rows and an index t1i1 on column x.
Sources: test/evidence/slt_lang_dropindex.test:1-16 test/evidence/slt_lang_droptable.test:1-16 test/evidence/slt_lang_droptrigger.test:1-16 test/evidence/slt_lang_dropview.test:1-16
DROP INDEX Tests
The DROP INDEX tests validate index removal behavior and are located in test/evidence/slt_lang_dropindex.test
Basic DROP INDEX Behavior
Sources: test/evidence/slt_lang_dropindex.test:19-34
Cross-Database Compatibility
DROP INDEX syntax differs significantly across database platforms. The tests use skipif mssql directives because Microsoft SQL Server requires different syntax:
skipif mssql
statement ok
DROP INDEX t1i1;
In MSSQL, the syntax would be DROP INDEX t1.t1i1 (table.index format), which is incompatible with the standard SQLite syntax tested here.
Sources: test/evidence/slt_lang_dropindex.test:22-34
DROP TABLE Tests
The DROP TABLE tests validate table removal and cascading deletion behavior, located in test/evidence/slt_lang_droptable.test
graph TB
DROP_TABLE["DROP TABLE t1"]
subgraph "Affected Objects"
TABLE["Table t1\nRemoved from schema"]
INDICES["All indices on t1\n(e.g., t1i1)"]
TRIGGERS["All triggers on t1\n(e.g., t1r1)"]
DATA["Table data\nRemoved from disk"]
end
DROP_TABLE --> TABLE
DROP_TABLE --> INDICES
DROP_TABLE --> TRIGGERS
DROP_TABLE --> DATA
INDICES -.-> IDX_ERROR["Subsequent DROP INDEX t1i1\nproduces error"]
TRIGGERS -.-> TRG_ERROR["Subsequent DROP TRIGGER t1r1\nproduces error"]
Cascading Deletion
A critical aspect of DROP TABLE is the automatic deletion of associated database objects:
Sources: test/evidence/slt_lang_droptable.test:36-41
The test validates cascading deletion by attempting to drop the index after the table:
statement ok
DROP TABLE t1
# this should error, as was dropped with table
statement error
DROP INDEX t1i1;
This verifies evidence marker R-33950-57093: "All indices and triggers associated with the table are also deleted."
Sources: test/evidence/slt_lang_droptable.test:36-41
IF EXISTS Clause
The DROP TABLE tests include validation of the IF EXISTS clause:
statement ok
CREATE TABLE t1( x INTEGER, y VARCHAR(8) )
skipif mssql
statement ok
DROP TABLE IF EXISTS t1
skipif mssql
statement ok
DROP TABLE IF EXISTS t1
The second DROP TABLE IF EXISTS t1 succeeds without error because the IF EXISTS clause suppresses the error that would normally occur when dropping a non-existent table.
Sources: test/evidence/slt_lang_droptable.test:43-55
Error Conditions
The tests validate three error scenarios:
- Already dropped table : Attempting to drop a table that was previously dropped in the same test session
- Non-existent table : Attempting to drop a table name that was never created
- Cascaded object : Attempting to drop an index that was automatically removed when its parent table was dropped
Sources: test/evidence/slt_lang_droptable.test:25-31 test/evidence/slt_lang_droptable.test:39-41
DROP TRIGGER Tests
The DROP TRIGGER tests validate trigger removal and automatic deletion behavior, located in test/evidence/slt_lang_droptrigger.test
Platform Compatibility
DROP TRIGGER tests are not compatible with Microsoft SQL Server:
onlyif mssql
halt
This halts execution on MSSQL platforms because the trigger syntax is significantly different from SQLite.
Sources: test/evidence/slt_lang_droptrigger.test:18-19
Trigger Creation and Deletion
Sources: test/evidence/slt_lang_droptrigger.test:24-36 test/evidence/slt_lang_droptrigger.test:44-52
Automatic Trigger Deletion
The tests validate that triggers are automatically deleted when their associated table is dropped:
statement ok
CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END;
statement ok
DROP TABLE t1
# already deleted when table dropped
statement error
DROP TRIGGER t1r1
This verifies evidence marker R-37808-62273: "triggers are automatically dropped when the associated table is dropped."
Sources: test/evidence/slt_lang_droptrigger.test:41-52
DROP VIEW Tests
The DROP VIEW tests validate view removal while ensuring underlying data integrity, located in test/evidence/slt_lang_dropview.test
View Definition Removal
Sources: test/evidence/slt_lang_dropview.test:27-31
Data Preservation Validation
The tests explicitly verify that dropping a view does not affect data in the underlying base tables:
statement ok
CREATE VIEW view2 AS SELECT x FROM t1 WHERE x=0
query I rowsort label-0
SELECT x FROM view2
----
0
statement ok
DROP VIEW view2
query I rowsort label-0
SELECT x FROM t1 WHERE x=0
----
0
This sequence:
- Creates view
view2that filterst1for rows wherex=0 - Queries through the view, confirming result is
0 - Drops the view
- Directly queries the base table with the same filter, confirming data is still
0
This verifies evidence marker R-00359-41639: "no actual data in the underlying base tables is modified."
Sources: test/evidence/slt_lang_dropview.test:44-59
Object Resolution
The tests validate standard object resolution for view names:
statement ok
CREATE VIEW view1 AS SELECT x FROM t1 WHERE x>0
statement ok
DROP VIEW view1
This verifies evidence marker R-18673-21346 regarding view-name and optional schema-name resolution using standard object resolution procedures.
Sources: test/evidence/slt_lang_dropview.test:22-31
Error Testing Matrix
All DROP statement tests validate a consistent set of error conditions:
| Error Scenario | Test Directive | Expected Behavior | Evidence |
|---|---|---|---|
| Drop already-dropped object | statement error | Error raised | Basic DROP behavior |
| Drop non-existent object | statement error | Error raised | Basic DROP behavior |
| Drop cascaded object | statement error | Error raised | Cascading deletion validation |
| Drop with IF EXISTS (exists) | statement ok | Success, object removed | IF EXISTS clause |
| Drop with IF EXISTS (not exists) | statement ok | Success, no error | IF EXISTS clause |
Sources: test/evidence/slt_lang_dropindex.test:26-34 test/evidence/slt_lang_droptable.test:25-41 test/evidence/slt_lang_droptrigger.test:30-36 test/evidence/slt_lang_dropview.test:33-39
flowchart TD
START["Execute DROP test"] --> CHECK_PLATFORM{"Platform?"}
CHECK_PLATFORM -->|SQLite| RUN_ALL["Run all tests\nStandard DROP syntax"]
CHECK_PLATFORM -->|MSSQL| SKIP_SOME["skipif mssql directives\nSkip incompatible tests"]
SKIP_SOME --> DROP_INDEX_SKIP["DROP INDEX skipped\nDifferent syntax required"]
SKIP_SOME --> IF_EXISTS_SKIP["IF EXISTS clause skipped\nNot supported in MSSQL"]
SKIP_SOME --> DROP_TRIGGER_HALT["DROP TRIGGER halted\nIncompatible syntax"]
RUN_ALL --> COMPLETE["All tests executed"]
DROP_INDEX_SKIP --> COMPLETE
IF_EXISTS_SKIP --> COMPLETE
DROP_TRIGGER_HALT --> HALT["Test execution stopped"]
Cross-Database Compatibility Summary
The DROP statement tests use conditional directives to handle platform-specific differences:
Sources: test/evidence/slt_lang_dropindex.test:22-34 test/evidence/slt_lang_droptable.test:49-55 test/evidence/slt_lang_droptrigger.test:18-19
Platform-Specific Behaviors
| Feature | SQLite | MSSQL | Notes |
|---|---|---|---|
| DROP INDEX syntax | DROP INDEX idx_name | DROP INDEX table.idx_name | Tests skip MSSQL |
| DROP TABLE IF EXISTS | Supported | Not supported | Tests skip MSSQL |
| DROP TRIGGER | Full support | Different syntax | Tests halt on MSSQL |
| DROP VIEW | Supported | Supported | No platform differences |
| Cascading deletion | Automatic | Automatic | Consistent behavior |
Sources: test/evidence/slt_lang_dropindex.test22 test/evidence/slt_lang_droptable.test49 test/evidence/slt_lang_droptrigger.test:18-19
graph TB
subgraph "Test Coverage"
BASIC["Basic DROP operations\nSuccess cases"]
ERRORS["Error conditions\nDouble-drop, non-existent"]
CASCADE["Cascading deletion\nIndices, triggers"]
IF_EXISTS["IF EXISTS clause\nError suppression"]
DATA_INTEGRITY["Data integrity\nView removal validation"]
end
subgraph "Test Files"
DROP_INDEX_FILE["slt_lang_dropindex.test"]
DROP_TABLE_FILE["slt_lang_droptable.test"]
DROP_TRIGGER_FILE["slt_lang_droptrigger.test"]
DROP_VIEW_FILE["slt_lang_dropview.test"]
end
DROP_INDEX_FILE --> BASIC
DROP_INDEX_FILE --> ERRORS
DROP_TABLE_FILE --> BASIC
DROP_TABLE_FILE --> ERRORS
DROP_TABLE_FILE --> CASCADE
DROP_TABLE_FILE --> IF_EXISTS
DROP_TRIGGER_FILE --> BASIC
DROP_TRIGGER_FILE --> ERRORS
DROP_TRIGGER_FILE --> CASCADE
DROP_VIEW_FILE --> BASIC
DROP_VIEW_FILE --> ERRORS
DROP_VIEW_FILE --> DATA_INTEGRITY
Test Execution Summary
The DROP statement tests validate the complete lifecycle of database object removal:
Sources: test/evidence/slt_lang_dropindex.test:1-35 test/evidence/slt_lang_droptable.test:1-56 test/evidence/slt_lang_droptrigger.test:1-53 test/evidence/slt_lang_dropview.test:1-60
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Data Manipulation Language (DML) Tests
Relevant source files
Purpose and Scope
This document describes the Data Manipulation Language (DML) test suite within the SQL Language Evidence Tests category. DML tests validate the behavior of SQL statements that modify data within existing database tables, specifically:
- UPDATE statements for modifying existing rows
- REPLACE and INSERT OR REPLACE statements for insert-or-update operations
These tests ensure compliance with SQL standards and SQLite-specific behaviors for data manipulation operations. For information about creating or dropping database objects, see Data Definition Language (DDL) Tests-tests). For query validation, see Query Operator Tests.
Sources: test/evidence/slt_lang_update.test:1-198 test/evidence/slt_lang_replace.test:1-76
Test File Organization
The DML test suite consists of two primary test files located in the test/evidence/ directory:
| Test File | Primary Focus | Line Count | Cross-DB Directives |
|---|---|---|---|
slt_lang_update.test | UPDATE statement behavior | 198 | Yes (MSSQL) |
slt_lang_replace.test | REPLACE and INSERT OR REPLACE | 76 | Yes (MSSQL, Oracle, MySQL) |
Both files employ the SQL Logic Test format with evidence markers that reference specific requirements from the SQLite documentation.
Sources: test/evidence/slt_lang_update.test1 test/evidence/slt_lang_replace.test1
UPDATE Statement Test Coverage
Core UPDATE Functionality
The slt_lang_update.test file validates fundamental UPDATE statement behavior through a series of progressive tests that build upon a simple two-column table schema:
Sources: test/evidence/slt_lang_update.test:3-16
graph TD
Setup["Table Setup\nCREATE TABLE t1(x INTEGER, y VARCHAR(8))\nCREATE INDEX t1i1 ON t1(x)"]
Setup --> InitData["Initial Data\nINSERT INTO t1 VALUES(1,'true')\nINSERT INTO t1 VALUES(0,'false')\nINSERT INTO t1 VALUES(NULL,'NULL')"]
InitData --> BasicUpdate["Basic UPDATE Tests\nEvidence: R-38515-45264"]
InitData --> WhereClause["WHERE Clause Tests\nEvidence: R-58095-46013, R-58129-20729"]
InitData --> MultiAssign["Multiple Assignment Tests\nEvidence: R-34751-18293"]
InitData --> SelfRef["Self-Referencing Tests\nEvidence: R-36239-04077, R-04558-24451"]
BasicUpdate --> Validate["Query and Validate Results"]
WhereClause --> Validate
MultiAssign --> Validate
SelfRef --> Validate
WHERE Clause Behavior
The tests validate three key WHERE clause scenarios:
- Unconditional UPDATE (no WHERE clause) - All rows affected
- Conditional UPDATE - Only matching rows affected
- Non-matching WHERE - Zero rows affected (not an error)
Sources: test/evidence/slt_lang_update.test:43-70
Multiple Column Assignment
Test case at test/evidence/slt_lang_update.test:83-108 validates the behavior when the same column appears multiple times in the assignment list. According to Evidence R-34751-18293, only the rightmost assignment is used:
| Test Step | SQL Statement | Expected Behavior |
|---|---|---|
| Initial | UPDATE t1 SET x=3, x=4, x=5 | All rows have x=5 |
| Verify x=3 | SELECT count(*) FROM t1 WHERE x=3 | Returns 0 |
| Verify x=4 | SELECT count(*) FROM t1 WHERE x=4 | Returns 0 |
| Verify x=5 | SELECT count(*) FROM t1 WHERE x=5 | Returns 3 |
Note: This test is skipped on MSSQL platforms using the skipif mssql directive, as MSSQL treats multiple assignments to the same column as an error.
Sources: test/evidence/slt_lang_update.test:83-108
Self-Referencing Expressions
Tests validate that scalar expressions in UPDATE statements can reference columns from the row being updated, and that all expressions are evaluated before any assignments are made:
Sources: test/evidence/slt_lang_update.test:127-138
sequenceDiagram
participant SQL as "UPDATE t1 SET x=x+2"
participant Eval as "Expression Evaluator"
participant Store as "Row Storage"
Note over SQL,Store: Evidence: R-36239-04077, R-04558-24451
SQL->>Eval: For each row
Eval->>Store: Read current x value
Store-->>Eval: x = 2
Eval->>Eval: Evaluate x+2 = 4
Note over Eval: All expressions evaluated\nBEFORE assignments
Eval->>Store: Write x = 4
Store-->>SQL: Row updated
Unmodified Columns
Evidence R-40472-60438 validates that columns not mentioned in the assignment list remain unchanged. Test test/evidence/slt_lang_update.test:109-124 verifies this by updating column x while monitoring that column y retains its original value.
Sources: test/evidence/slt_lang_update.test:109-124
REPLACE Statement Test Coverage
REPLACE as INSERT OR REPLACE Alias
The slt_lang_replace.test file validates Evidence R-03421-22330, which states that "The REPLACE command is an alias for the 'INSERT OR REPLACE' variant of the INSERT command."
Sources: test/evidence/slt_lang_replace.test:21-56
graph LR
subgraph "Test Table Schema"
T1["t1(x INTEGER PRIMARY KEY, y VARCHAR(16))"]
end
subgraph "Test Progression"
Insert["INSERT INTO t1 VALUES(2, 'insert')"]
InsertOrReplace["INSERT OR REPLACE INTO t1 VALUES(2, 'insert or replace')"]
Replace["REPLACE INTO t1 VALUES(2, 'replace')"]
Insert -->|Verify: x=2, y='insert'| V1["Query: SELECT x,y FROM t1 WHERE x=2"]
InsertOrReplace -->|Verify: x=2, y='insert or replace'| V2["Query: SELECT x,y FROM t1 WHERE x=2"]
Replace -->|Verify: x=2, y='replace'| V3["Query: SELECT x,y FROM t1 WHERE x=2"]
end
T1 --> Insert
Test Execution Pattern
The REPLACE tests follow a consistent pattern validating both update and insert semantics:
| Operation Type | SQL Statement | Primary Key Exists? | Expected Behavior |
|---|---|---|---|
| Initial INSERT | INSERT INTO t1 VALUES(2, 'insert') | No | New row created |
| INSERT OR REPLACE | INSERT OR REPLACE INTO t1 VALUES(2, 'insert or replace') | Yes | Existing row replaced |
| REPLACE | REPLACE INTO t1 VALUES(2, 'replace') | Yes | Existing row replaced |
| INSERT OR REPLACE (new) | INSERT OR REPLACE INTO t1 VALUES(3, 'insert or replace (new)') | No | New row created |
| REPLACE (new) | REPLACE INTO t1 VALUES(4, 'replace (new)') | No | New row created |
Sources: test/evidence/slt_lang_replace.test:24-76
Cross-Platform Compatibility
flowchart TD
Start["Test File Execution Begins"]
Start --> CheckMSSQL{"Platform\nMSSQL?"}
CheckMSSQL -->|Yes| HaltMSSQL["halt\n(slt_lang_replace.test:1-3)"]
CheckMSSQL -->|No| CheckOracle{"Platform\nOracle?"}
CheckOracle -->|Yes| HaltOracle["halt\n(slt_lang_replace.test:5-7)"]
CheckOracle -->|No| RunTests["Execute Test Cases"]
RunTests --> MultiAssignCheck{"Test: Multiple\ncolumn assignment?"}
MultiAssignCheck -->|Yes| SkipMSSQL["skipif mssql\n(slt_lang_update.test:87)"]
MultiAssignCheck -->|No| Execute["Execute Test"]
SkipMSSQL -->|MSSQL| Skip["Skip Test"]
SkipMSSQL -->|Other DB| Execute
RunTests --> ReplaceCheck{"Test: INSERT OR\nREPLACE syntax?"}
ReplaceCheck -->|Yes| SkipMySQL["skipif mysql\n(slt_lang_replace.test:37,57)"]
ReplaceCheck -->|No| Execute
SkipMySQL -->|MySQL| Skip
SkipMySQL -->|Other DB| Execute
Platform Exclusions
Both DML test files include conditional execution directives to handle database platform differences:
Sources: test/evidence/slt_lang_replace.test:1-7 test/evidence/slt_lang_update.test87 test/evidence/slt_lang_replace.test:37-57
Platform-Specific Behavior Matrix
| Database Platform | UPDATE Multiple Assignment | REPLACE Syntax | INSERT OR REPLACE Syntax |
|---|---|---|---|
| SQLite | ✓ Supported (rightmost wins) | ✓ Supported | ✓ Supported |
| MySQL | ✓ Supported | ✓ Supported | ✗ Not supported |
| MSSQL | ✗ Error on multiple assignment | ✗ Entire file skipped | ✗ Entire file skipped |
| Oracle | N/A (no tests) | ✗ Entire file skipped | ✗ Entire file skipped |
| PostgreSQL | ✓ Supported | N/A (no directives) | N/A (no directives) |
Sources: test/evidence/slt_lang_update.test:87-108 test/evidence/slt_lang_replace.test:1-66
Evidence Markers and Validation
UPDATE Statement Evidence Coverage
The UPDATE tests validate the following evidence requirements from SQLite documentation:
| Evidence ID | Requirement Description | Test Location |
|---|---|---|
| R-38515-45264 | UPDATE modifies subset of rows in table | test/evidence/slt_lang_update.test:19-35 |
| R-55869-30521 | UPDATE without WHERE affects all rows (TBD) | test/evidence/slt_lang_update.test:42-51 |
| R-58095-46013 | UPDATE with WHERE affects only matching rows | test/evidence/slt_lang_update.test:53-62 |
| R-58129-20729 | Non-matching WHERE is not an error | test/evidence/slt_lang_update.test:64-70 |
| R-40598-36595 | Named columns set to evaluated expressions | test/evidence/slt_lang_update.test:71-82 |
| R-34751-18293 | Rightmost assignment wins for duplicate columns | test/evidence/slt_lang_update.test:83-108 |
| R-40472-60438 | Unlisted columns remain unmodified | test/evidence/slt_lang_update.test:109-124 |
| R-36239-04077 | Expressions may refer to row being updated | test/evidence/slt_lang_update.test:127-130 |
| R-04558-24451 | All expressions evaluated before assignments | test/evidence/slt_lang_update.test:129-138 |
Sources: test/evidence/slt_lang_update.test:19-138
REPLACE Statement Evidence Coverage
| Evidence ID | Requirement Description | Test Location |
|---|---|---|
| R-03421-22330 | REPLACE is alias for INSERT OR REPLACE | test/evidence/slt_lang_replace.test:21-76 |
Sources: test/evidence/slt_lang_replace.test:21-76
erDiagram
t1 {INTEGER x PK\nVARCHAR_8 y}
t1_index["t1i1"] {INDEX_ON x}
t1 ||--o{ t1_index : indexed by
Test Data Patterns
UPDATE Test Dataset
The UPDATE tests operate on a minimal dataset designed to test NULL handling, boolean-like values, and indexed columns:
Initial data:
- Row 1:
x=1, y='true' - Row 2:
x=0, y='false' - Row 3:
x=NULL, y='NULL'
Sources: test/evidence/slt_lang_update.test:3-16
erDiagram
t1 {INTEGER x PK "PRIMARY KEY"\nVARCHAR_16 y}
REPLACE Test Dataset
The REPLACE tests use a simpler schema with a primary key constraint to test upsert behavior:
Initial data:
- Row 1:
x=1, y='true' - Row 2:
x=0, y='false'
Sources: test/evidence/slt_lang_replace.test:11-18
Hash Threshold Configuration
Both DML test files specify hash-threshold 8 at the beginning:
This directive controls when the test harness switches from storing individual result rows to using hash-based comparison for large result sets. A threshold of 8 means result sets with more than 8 rows will be validated using hash comparison instead of row-by-row comparison.
Sources: test/evidence/slt_lang_update.test1 test/evidence/slt_lang_replace.test9
Unimplemented Evidence (TBD)
The UPDATE test file contains several evidence markers prefixed with TBD-EVIDENCE-OF, indicating requirements that are documented but not yet tested:
- Conflict clause handling (R-12619-24112)
- Trigger-specific UPDATE restrictions (R-12123-54095, R-09690-36749, R-06085-13761, R-29512-54644)
- INDEXED BY/NOT INDEXED in triggers (R-19619-42762)
- LIMIT and ORDER BY clauses (R-57359-59558, R-59581-44104, R-58862-44169, R-63582-45120, R-18628-11938, R-30955-38324, R-19486-35828, R-10927-26133)
These represent advanced UPDATE features that require additional test infrastructure or are only available with specific compile-time options like SQLITE_ENABLE_UPDATE_DELETE_LIMIT.
Sources: test/evidence/slt_lang_update.test:139-198
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Query Operator Tests
Relevant source files
Purpose and Scope
This document describes the query operator tests in the evidence test suite, focusing on SQL operators used in SELECT queries. These tests validate the behavior of IN/NOT IN operators and aggregate functions across different database platforms.
For detailed coverage of specific operators:
- IN/NOT IN operator behavior with NULL values and empty sets: see IN and NOT IN Operator Tests
- Aggregate function behavior with DISTINCT and NULL handling: see Aggregate Function Tests
- Data manipulation operations (UPDATE, REPLACE): see Data Manipulation Language (DML) Tests-tests)
Test File Organization
Query operator tests are located in the test/evidence/ directory and organized by operator type:
| Test File | Primary Focus | Database Support |
|---|---|---|
in1.test | IN/NOT IN operators with empty sets, NULL values, and table operands | SQLite, MySQL (limited) |
in2.test | IN/NOT IN operator behavior matrix validation | All platforms (with conditional execution) |
slt_lang_aggfunc.test | Aggregate functions with DISTINCT keyword | SQLite only |
Sources: test/evidence/in1.test:1-1156 test/evidence/in2.test:1-314 test/evidence/slt_lang_aggfunc.test:1-500
Test File to Operator Mapping
Sources: test/evidence/in1.test:11-20 test/evidence/in1.test:329-336 test/evidence/in2.test:2-21 test/evidence/in2.test:292-295 test/evidence/slt_lang_aggfunc.test:24-27
IN and NOT IN Operator Tests
Test Coverage
The IN/NOT IN operator tests validate compliance with SQL specification evidence tags, particularly focusing on edge cases not commonly tested:
Empty Right-Hand Side (RHS)
- Tests validate SQLite's non-standard support for empty lists:
SELECT 1 IN () - Most databases (MySQL, MSSQL, Oracle) skip these tests via conditional directives
- Evidence tag:
R-52275-55503andR-64309-54027
Table-as-Operand Syntax
- SQLite allows direct table references:
SELECT 1 IN t1 - Other databases require subquery form:
SELECT 1 IN (SELECT * FROM t1) - Tests use
onlyif sqlitedirectives for direct table syntax
NULL Value Handling
- Comprehensive matrix testing of NULL in left operand, right operand, and both
- Tests validate three-valued logic (true, false, NULL) for all combinations
Sources: test/evidence/in1.test:11-20 test/evidence/in1.test:22-26 test/evidence/in1.test:69-73 test/evidence/in2.test:2-21
Behavior Matrix Implementation
Sources: test/evidence/in1.test:329-336 test/evidence/in2.test:14-21 test/evidence/in1.test:795-902
Test Execution Pattern
The tests systematically cover each row of the behavior matrix documented in evidence tag R-50221-42915:
| Left NULL | Right has NULL | RHS Empty | Value Found | IN Result | NOT IN Result | Test Lines |
|---|---|---|---|---|---|---|
| No | No | No | No | false | true | in1.test:339-357 |
| Any | Any | Yes | Any | false | true | in1.test:22-64 |
| No | Any | No | Yes | true | false | in1.test:555-791 |
| No | Yes | No | No | NULL | NULL | in1.test:795-901 |
| Yes | Any | No | Any | NULL | NULL | in1.test:905-1155 |
Sources: test/evidence/in1.test:329-357 test/evidence/in1.test:555-791 test/evidence/in1.test:795-1155
Aggregate Function Tests
graph LR
subgraph "Aggregate Functions Tested"
COUNT["count(x)\ncount(DISTINCT x)\ncount(*)"]
AVG["avg(x)\navg(DISTINCT x)"]
SUM["sum(x)\nsum(DISTINCT x)"]
TOTAL["total(x)\ntotal(DISTINCT x)"]
MIN["min(x)\nmin(DISTINCT x)"]
MAX["max(x)\nmax(DISTINCT x)"]
CONCAT["group_concat(x)\ngroup_concat(x,sep)"]
end
subgraph "Test Aspects"
DIST["DISTINCT filtering\nR-00466-56349"]
NULL_HAND["NULL handling\nPer-function rules"]
TYPE_CONV["Type conversion\nString to number"]
RETURN_TYPE["Return type\nInteger vs Float"]
end
COUNT --> DIST
AVG --> DIST
SUM --> DIST
TOTAL --> DIST
MIN --> DIST
MAX --> DIST
CONCAT --> DIST
COUNT --> NULL_HAND
AVG --> NULL_HAND
SUM --> NULL_HAND
AVG --> RETURN_TYPE
SUM --> RETURN_TYPE
TOTAL --> RETURN_TYPE
Supported Functions
The slt_lang_aggfunc.test file validates all standard SQL aggregate functions:
Sources: test/evidence/slt_lang_aggfunc.test:21-22 test/evidence/slt_lang_aggfunc.test:24-62
DISTINCT Keyword Validation
All aggregate functions support the DISTINCT keyword, which filters duplicate values before aggregation:
Evidence Tag:R-00466-56349
- Tests verify:
count(DISTINCT x),avg(DISTINCT x),sum(DISTINCT x), etc. - Validation approach: Insert duplicate values, compare results with and without DISTINCT
Example Test Pattern:
INSERT INTO t1 VALUES(2,'true')
INSERT INTO t1 VALUES(2,'true') -- Duplicate value
SELECT count(DISTINCT x) FROM t1 -- Should count 2 once
Sources: test/evidence/slt_lang_aggfunc.test:24-62 test/evidence/slt_lang_aggfunc.test:71-81
NULL Handling Per Function
Each aggregate function has distinct NULL handling behavior validated by the tests:
| Function | NULL Inputs | Returns | Evidence Tag | Test Lines |
|---|---|---|---|---|
count(x) | Ignored | Count of non-NULL | R-34280-42283 | slt_lang_aggfunc.test:198-220 |
count(*) | Counted | Total rows | R-13776-21310 | slt_lang_aggfunc.test:222-234 |
avg(x) | Ignored | NULL if all NULL, else float | R-40597-22164 | slt_lang_aggfunc.test:184-196 |
sum(x) | Ignored | NULL if all NULL | R-44223-43966 | slt_lang_aggfunc.test:396-408 |
total(x) | Ignored | 0.0 if all NULL | R-44223-43966 | slt_lang_aggfunc.test:409-418 |
min(x) | Ignored | NULL if all NULL | R-10396-30188 | slt_lang_aggfunc.test:358-370 |
max(x) | Ignored | NULL if all NULL | R-50775-16353 | slt_lang_aggfunc.test:311-323 |
group_concat(x) | Ignored | Concatenates non-NULL | R-56088-25150 | slt_lang_aggfunc.test:236-249 |
Sources: test/evidence/slt_lang_aggfunc.test:198-234 test/evidence/slt_lang_aggfunc.test:311-370 test/evidence/slt_lang_aggfunc.test:396-418
Type Conversion and Return Types
The tests validate type coercion and return type behavior:
String to Number Conversion (Evidence:R-29052-00975):
- Non-numeric strings are interpreted as 0
- Tests use VARCHAR column
ywith values like'true','false','NULL' - Aggregate functions treat these as 0
Return Type Rules:
avg(x): Always returns float if any non-NULL input (Evidence:R-17177-10067)total(x): Always returns float (Evidence:R-07734-01023)sum(x): Returns integer if all inputs are integers, else float (Evidence:R-19660-56479,R-33611-59266)
Test Implementation:
-- avg always returns float
SELECT avg(x) FROM t1 -- Returns 1.250 (float) not 1
-- sum returns integer with integer inputs
SELECT sum(x) FROM t1 -- Returns 5 (integer)
-- sum returns float with any float input
INSERT INTO t1 VALUES(4.0,'true')
SELECT sum(x) FROM t1 -- Returns 9.000 (float)
Sources: test/evidence/slt_lang_aggfunc.test:92-124 test/evidence/slt_lang_aggfunc.test:168-182 test/evidence/slt_lang_aggfunc.test:420-464
flowchart TD
START["Test Execution Begins"] --> CHECK_PLATFORM{"Platform\nDetection"}
CHECK_PLATFORM -->|onlyif mssql halt| SKIP_MSSQL["Skip entire file\n(MSSQL)"]
CHECK_PLATFORM -->|onlyif oracle halt| SKIP_ORACLE["Skip entire file\n(Oracle)"]
CHECK_PLATFORM -->|SQLite or MySQL| CONTINUE["Continue execution"]
CONTINUE --> CHECK_FEATURE{"Feature\nCheck"}
CHECK_FEATURE -->|onlyif sqlite| SQLITE_ONLY["Execute SQLite-only tests\n- Empty RHS\n- Table-as-operand"]
CHECK_FEATURE -->|skipif mysql| SKIP_MYSQL["Skip MySQL-incompatible\n- Empty RHS\n- Some NULL tests"]
CHECK_FEATURE -->|Common SQL| COMMON["Execute cross-platform\ncompatible tests"]
SQLITE_ONLY --> VALIDATE["Validate Results"]
SKIP_MYSQL --> VALIDATE
COMMON --> VALIDATE
SKIP_MSSQL --> END["Test Complete"]
SKIP_ORACLE --> END
VALIDATE --> END
Cross-Database Compatibility
Platform-Specific Test Execution
The query operator tests use conditional directives to handle database differences:
Sources: test/evidence/in1.test:3-9 test/evidence/in1.test:22-26 test/evidence/in1.test:69-73
Platform Compatibility Matrix
| Feature | SQLite | MySQL | MSSQL | Oracle | PostgreSQL | Directive Used |
|---|---|---|---|---|---|---|
Empty RHS () | ✓ | ✗ | ✗ | ✗ | ✗ | onlyif sqlite |
| Table-as-operand | ✓ | ✗ | ✗ | ✗ | ✗ | onlyif sqlite |
| Subquery IN | ✓ | ✓ | ✓ | ✓ | ✓ | No directive |
| Aggregate DISTINCT | ✓ | Limited | Limited | Limited | ✓ | skipif sqlite / halt |
| TEXT UNIQUE index | ✓ | Requires length | ✓ | ✓ | ✓ | skipif/onlyif mysql |
Sources: test/evidence/in1.test:22-64 test/evidence/in1.test:398-427 test/evidence/slt_lang_aggfunc.test:18-19
MySQL-Specific Workarounds
MySQL requires special handling in several test scenarios:
UNIQUE Index on TEXT Columns:
- SQLite:
CREATE TABLE t7(a TEXT UNIQUE) - MySQL:
CREATE TABLE t7(a TEXT, UNIQUE (a(1)))-- Requires length prefix
NULL in Subqueries:
- Some
NULL IN (SELECT ...)tests are skipped on MySQL due to known failures - Tests marked with
skipif mysqlcomments
Sources: test/evidence/in1.test:398-404 test/evidence/in1.test:421-427 test/evidence/in1.test:951-975
Test Result Validation
Label-Based Test Groups
Tests use label markers to group related assertions that should produce identical results:
query I nosort label-1
SELECT 1 IN t1 -- SQLite-only direct table syntax
----
0
query I nosort label-1
SELECT 1 IN (SELECT * FROM t1) -- Standard subquery syntax
----
0
Both queries share label-1 and must return the same result (0), validating that SQLite's table-as-operand syntax is equivalent to standard subquery form.
Sources: test/evidence/in1.test:69-78 test/evidence/in1.test:441-461
Test Data Setup Pattern
Query operator tests follow a consistent setup pattern:
- Create tables with different index configurations (UNIQUE, PRIMARY KEY, no index)
- Populate with base data and NULL-containing variants
- Execute operator tests against each table variant
- Validate results across table types are consistent
Table Types Used:
t1,t2,t3: Base tables (no index, PRIMARY KEY, UNIQUE)t4,t5,t6: Integer tables with different indexest4n,t6n: Same as above but with NULL values addedt7,t8: Text tables with different indexest7n,t8n: Text tables with NULL values
Sources: test/evidence/in1.test:66-161 test/evidence/in1.test:359-439
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
IN and NOT IN Operator Tests
Relevant source files
Purpose and Scope
This page documents the test corpus for SQL IN and NOT IN operators, specifically covering the behavior defined by the SQL specification and SQLite's extensions. These tests validate how IN and NOT IN operators behave when operating on empty sets, NULL values, and various combinations of left and right operands across different data types.
The tests verify compliance with four key SQL language specifications regarding IN/NOT IN operator behavior, particularly focusing on:
- Empty set handling (a SQLite-specific extension)
- NULL value interactions
- Scalar vs. row value expressions
- Cross-database compatibility differences
For information about aggregate functions tested with these operators, see Aggregate Function Tests. For broader query operator testing, see Query Operator Tests.
Sources: test/evidence/in1.test:1-1156 test/evidence/in2.test:1-314
Evidence Specifications Tested
The test files validate four specific evidence codes from the SQLite documentation that define the normative behavior of IN and NOT IN operators:
| Evidence Code | Specification |
|---|---|
| R-52275-55503 | When the right operand is an empty set, IN returns false and NOT IN returns true, regardless of the left operand (even if NULL) |
| R-64309-54027 | SQLite allows empty lists on the right-hand side of IN/NOT IN operators, while most other SQL databases and SQL92 standard require at least one element |
| R-50221-42915 | Defines the complete behavior matrix for IN/NOT IN operators based on left operand NULL status, right operand NULL content, empty set status, and match status |
| R-35033-20570 | The subquery on the right of IN/NOT IN must be a scalar subquery if the left expression is not a row value expression |
Sources: test/evidence/in1.test:11-19 test/evidence/in2.test:2-21 test/evidence/in2.test:292-294
Behavior Matrix
The IN and NOT IN operators follow a deterministic five-row decision matrix that determines result values based on the characteristics of the left and right operands:
Sources: test/evidence/in1.test:329-336 test/evidence/in2.test:14-21
stateDiagram-v2
[*] --> CheckRHS
state CheckRHS {
[*] --> IsEmptySet : Right operand
IsEmptySet --> Row2 : Yes - Empty set
IsEmptySet --> CheckLHSNull : No - Has values
CheckLHSNull --> Row5 : LHS is NULL
CheckLHSNull --> CheckFound : LHS is not NULL
CheckFound --> CheckRHSNull : LHS not found in RHS
CheckFound --> Row3 : LHS found in RHS
CheckRHSNull --> Row4 : RHS contains NULL
CheckRHSNull --> Row1 : RHS has no NULL
}
Row1 --> Result1["IN : false NOT IN - true"]
Row2 --> Result2["IN : false NOT IN - true"]
Row3 --> Result3["IN : true NOT IN - false"]
Row4 --> Result4["IN : NULL NOT IN - NULL"]
Row5 --> Result5["IN : NULL NOT IN - NULL"]
Result1 --> [*]
Result2 --> [*]
Result3 --> [*]
Result4 --> [*]
Result5 --> [*]
note right of Row1
LHS not NULL, not found,
RHS has no NULL, non-empty
end note
note right of Row2
RHS is empty set
(SQLite extension)
end note
note right of Row3
LHS not NULL, found in RHS
(RHS may or may not have NULL)
end note
note right of Row4
LHS not NULL, not found,
but RHS contains NULL
end note
note right of Row5
LHS is NULL
(regardless of RHS content)
end note
Empty Set Handling (SQLite Extension)
SQLite allows empty lists () as the right-hand side of IN and NOT IN operators, which is an extension beyond SQL92 and most other database implementations. This behavior is tested extensively as Row 2 of the behavior matrix.
Empty Set Test Directives
The tests use conditional execution to handle databases that do not support empty sets:
flowchart TD
Start["Test: x IN ()"] --> CheckDB{"Database\nplatform?"}
CheckDB -->|SQLite| ExecuteTest["Execute:\nonlyif sqlite\nquery I nosort"]
CheckDB -->|MySQL| Skip1["Skip:\nskipif mysql\n(empty RHS)"]
CheckDB -->|MSSQL| Skip2["Skip:\nskipif mssql\n(empty RHS)"]
CheckDB -->|Oracle| Skip3["Skip:\nskipif oracle\n(empty RHS)"]
ExecuteTest --> Verify["Expect: 0 for IN\nExpect: 1 for NOT IN"]
Skip1 --> NextTest["Continue to next test"]
Skip2 --> NextTest
Skip3 --> NextTest
Verify --> NextTest
Examples of Empty Set Tests
| Left Operand | Operator | Expected Result | Test Coverage |
|---|---|---|---|
1 | IN () | 0 (false) | Integer literal |
1 | NOT IN () | 1 (true) | Integer literal |
NULL | IN () | 0 (false) | NULL literal |
NULL | NOT IN () | 1 (true) | NULL literal |
1.23 | IN () | 0 (false) | Float literal |
'hello' | IN () | 0 (false) | String literal |
x'303132' | IN () | 0 (false) | Blob literal |
Sources: test/evidence/in1.test:22-65 test/evidence/in1.test:227-327 test/evidence/in2.test:76-145
Table-as-Operand Syntax (SQLite Extension)
SQLite supports a non-standard syntax where a table name can be used directly as the right operand of IN/NOT IN, which is semantically equivalent to (SELECT * FROM table). This is tested extensively in in1.test.
graph TB
subgraph "SQLite-Specific Syntax"
TableDirect["x IN table_name"]
end
subgraph "Standard SQL Equivalent"
Subquery["x IN (SELECT * FROM table_name)"]
end
subgraph "Test Coverage"
EmptyTable["Empty tables:\nt1, t2, t3"]
PopulatedTable["Populated tables:\nt4, t5, t6"]
NullTable["Tables with NULL:\nt4n, t6n, t7n, t8n"]
end
TableDirect -.->|SQLite converts to| Subquery
EmptyTable --> TestEmpty["Labels 1-18:\nTest against empty sets"]
PopulatedTable --> TestFound["Labels 19-46:\nTest match/no-match"]
NullTable --> TestNull["Labels 47-72:\nTest NULL handling"]
style TableDirect fill:#f9f9f9
style Subquery fill:#f9f9f9
Test Table Schema
The test files create multiple tables with different configurations to test various scenarios:
| Table | Schema | Primary Key | Unique Constraint | Content | Purpose |
|---|---|---|---|---|---|
t1 | INTEGER | No | No | Empty | Basic empty table |
t2 | INTEGER | Yes | Implicit | Empty | Empty with PK |
t3 | INTEGER | No | Yes | Empty | Empty with UNIQUE |
t4 | INTEGER | No | Yes | 2, 3, 4 | Populated with UNIQUE |
t5 | INTEGER | Yes | Implicit | 2, 3, 4 | Populated with PK |
t6 | INTEGER | No | No | 2, 3, 4 | Populated, no constraints |
t4n | INTEGER | No | Yes | 2, 3, 4, NULL | UNIQUE + NULL values |
t6n | INTEGER | No | No | 2, 3, 4, NULL | No constraints + NULL |
t7 | TEXT | No | Yes | 'b', 'c', 'd' | Text values with UNIQUE |
t7n | TEXT | No | Yes | 'b', 'c', 'd', NULL | Text + NULL |
t8 | TEXT | No | No | 'b', 'c', 'd' | Text, no constraints |
t8n | TEXT | No | No | 'b', 'c', 'd', NULL | Text + NULL |
Sources: test/evidence/in1.test:66-206 test/evidence/in1.test:359-439
Test Structure and Organization
The tests are organized by the five rows of the behavior matrix, with each row testing specific combinations of conditions:
Sources: test/evidence/in1.test:1-1156 test/evidence/in2.test:1-314
graph LR
subgraph "in1.test Structure"
Header["File header:\nLines 1-10\nCross-DB directives"]
Row2Tests["Row 2: Empty set\nLines 11-327\nEvidence R-52275-55503\nR-64309-54027"]
MatrixHeader["Matrix documentation\nLines 329-336\nEvidence R-50221-42915"]
Row1Tests["Row 1: No match, no NULL\nLines 337-549"]
Row3Tests["Row 3: Match found\nLines 551-792"]
Row4Tests["Row 4: No match, has NULL\nLines 793-902"]
Row5Tests["Row 5: LHS is NULL\nLines 903-1156"]
end
Header --> Row2Tests
Row2Tests --> MatrixHeader
MatrixHeader --> Row1Tests
Row1Tests --> Row3Tests
Row3Tests --> Row4Tests
Row4Tests --> Row5Tests
Test Label System
The in1.test file uses a label system to group equivalent tests that should produce the same results. Labels range from label-1 to label-72 and allow cross-database validation where the same label should yield identical results across different syntactic forms.
Label Categories
| Label Range | Scenario | Table Forms | Subquery Form |
|---|---|---|---|
| 1-18 | Empty tables (rows 1-2) | t1, t2, t3 | SELECT * FROM t1/t2/t3 |
| 19-28 | No match in populated integer tables | t4, t5, t6 | Corresponding subqueries |
| 29-46 | Match found in populated tables | t4, t5, t6, t4n, t6n | Corresponding subqueries |
| 47-54 | No match with NULL present | t4n, t6n, t7n, t8n | Corresponding subqueries |
| 55-72 | LHS is NULL | All table variants | Corresponding subqueries |
Example Label Usage
Both queries with label-1 expect the same result (0), demonstrating that the table-as-operand syntax is equivalent to the subquery syntax.
Sources: test/evidence/in1.test:69-158
Cross-Database Compatibility
The tests handle significant differences between database platforms regarding IN/NOT IN operator support:
Platform Support Matrix
| Feature | SQLite | MySQL | MSSQL | Oracle | PostgreSQL |
|---|---|---|---|---|---|
Empty RHS () | ✓ Yes | ✗ No | ✗ No | ✗ No | ✗ No |
| Table-as-operand | ✓ Yes | ✗ No | ✗ No | ✗ No | ✗ No |
| Standard subquery | ✓ Yes | ✓ Yes | ✓ Yes | ✓ Yes | ✓ Yes |
| NULL IN (SELECT ...) | ✓ Yes | ⚠ Partial | ✓ Yes | ✓ Yes | ✓ Yes |
Database-Specific Directives
MySQL-Specific Handling
MySQL has additional compatibility issues noted in the test file with skip directives:
These failures are documented at specific lines where MySQL's NULL handling in subqueries diverges from the expected behavior.
Sources: test/evidence/in1.test:1-10 test/evidence/in1.test:951-975 test/evidence/in2.test:78-80 test/evidence/in2.test:303-310
graph TB
subgraph "Literal Value Tests"
IntLit["Integer: 1, 2, 3, 4"]
FloatLit["Float: 1.23"]
StringLit["String: 'hello', 'a', 'b'"]
BlobLit["Blob: x'303132'"]
NullLit["NULL literal"]
end
subgraph "Table Column Tests"
IntCol["INTEGER columns\nt1-t6, t4n, t6n"]
TextCol["TEXT columns\nt7, t8, t7n, t8n"]
MixedCol["VARCHAR (Oracle)\nOracle compatibility"]
end
IntLit --> EmptySetTests["Empty set tests\nlines 22-327"]
FloatLit --> EmptySetTests
StringLit --> EmptySetTests
BlobLit --> EmptySetTests
NullLit --> EmptySetTests
IntCol --> MatrixTests["Matrix behavior tests\nlines 337-1156"]
TextCol --> MatrixTests
MixedCol --> MatrixTests
Data Type Coverage
The tests validate IN/NOT IN behavior across multiple data types to ensure type-agnostic operator behavior:
Tested Data Types
Type-Specific Test Examples
| Data Type | Test Query | Expected Result | Line Reference |
|---|---|---|---|
| Integer | SELECT 1 IN (2,3,4) | 0 | in1.test:33-36 |
| Float | SELECT 1.23 IN t1 | 0 | in1.test:239-243 |
| String | SELECT 'hello' NOT IN () | 1 | in1.test:267-271 |
| Blob | SELECT x'303132' IN t1 | 0 | in1.test:307-311 |
| NULL | SELECT null IN () | 0 | in1.test:54-58 |
Sources: test/evidence/in1.test:22-327 test/evidence/in2.test:23-29 test/evidence/in2.test:398-404 test/evidence/in2.test:421-427
Scalar Subquery Validation
The test file in2.test includes validation that subqueries on the right side of IN/NOT IN must return a single column (scalar subquery) when the left expression is not a row value:
Valid and Invalid Subqueries
All multi-column subquery tests are expected to produce a statement error, validating evidence code R-35033-20570.
Sources: test/evidence/in2.test:292-314
sequenceDiagram
participant Runner as "Test Runner"
participant in1 as "in1.test"
participant in2 as "in2.test"
participant DB as "Database Engine"
Runner->>in1: Start execution
in1->>in1: Check: onlyif mssql (line 4)
in1->>in1: Check: onlyif oracle (line 8)
alt MSSQL or Oracle detected
in1->>Runner: halt - skip entire file
else SQLite or MySQL
in1->>DB: CREATE TABLE t1, t2, t3...
in1->>DB: Test empty set cases\n(onlyif sqlite directives)
DB-->>in1: Results for SQLite only
in1->>DB: INSERT test data into t4-t8
in1->>DB: Test matrix rows 1-5
DB-->>in1: Results
in1->>Runner: Complete - 1156 lines
end
Runner->>in2: Start execution
in2->>DB: CREATE TABLE t1(x, y)
in2->>DB: INSERT test data
in2->>DB: Test each matrix row\nwith WHERE clauses
DB-->>in2: Result counts
in2->>DB: Test scalar subquery validation\n(expect errors for multi-column)
DB-->>in2: Errors for invalid queries
in2->>Runner: Complete - 314 lines
Test Execution Flow
The following diagram shows how the test runner processes in1.test and in2.test:
Sources: test/evidence/in1.test:1-1156 test/evidence/in2.test:1-314
Summary of Test Coverage
The IN and NOT IN operator tests provide comprehensive validation across:
- 4 evidence specifications from SQLite documentation
- 5 rows in the behavior matrix covering all operator outcomes
- 12 test tables with varying schema configurations (primary keys, unique constraints, NULL values)
- 4 data types (integer, float, text, blob) plus NULL
- 72 labeled test groups ensuring syntax equivalence
- 300+ individual test queries across both test files
- 4 database platforms (full support on SQLite, partial on MySQL, excluded on MSSQL/Oracle)
The tests validate both standard SQL behavior and SQLite-specific extensions, ensuring compatibility and correctness across different database implementations.
Sources: test/evidence/in1.test:1-1156 test/evidence/in2.test:1-314
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Aggregate Function Tests
Relevant source files
Purpose and Scope
This document describes the aggregate function test suite in the SQL Logic Test corpus. The test file validates the behavior of SQLite's aggregate functions including count(), avg(), sum(), total(), min(), max(), and group_concat(). Tests cover standard aggregate operations, the DISTINCT keyword, NULL handling, type coercion, and return type behavior.
For query operator tests including IN/NOT IN operators, see IN and NOT IN Operator Tests. For other SQL language evidence tests, see SQL Language Evidence Tests.
Sources: test/evidence/slt_lang_aggfunc.test:1-500
Test File Overview
The aggregate function tests are located in a single file that validates compliance with SQLite documentation requirements. Each test includes "EVIDENCE-OF" comments referencing specific SQLite documentation clauses.
| Property | Value |
|---|---|
| File Location | test/evidence/slt_lang_aggfunc.test |
| Hash Threshold | 8 |
| Platform Support | SQLite only (halts on other databases) |
| Test Count | ~100+ individual queries |
| Primary Table | t1(x INTEGER, y VARCHAR(8)) |
Platform Execution:
Sources: test/evidence/slt_lang_aggfunc.test:1-19
Test Data Structure
The tests use a simple table structure with carefully designed data to validate edge cases:
Initial Test Data:
erDiagram
t1 {INTEGER x "Primary test column"\nVARCHAR y "Labels for filtering"}
t1_data {INTEGER x "1, 0, NULL, 2, 2, 4.0, 1<<63, 1<<63, -1"\nVARCHAR y "true, false, NULL, true, true, true, true, true, true"}
t1_index {INDEX t1i1 "ON t1(x) - created then dropped"}
t1 ||--|| t1_data : contains
t1 ||--o{ t1_index : has
| x | y | Purpose |
|---|---|---|
| 1 | 'true' | Non-zero integer |
| 0 | 'false' | Zero value |
| NULL | 'NULL' | NULL handling |
| 2 | 'true' | Duplicate value (added later) |
| 2 | 'true' | Duplicate value (added later) |
| 4.0 | 'true' | Floating point value |
| 1<<63 | 'true' | Large integer (overflow testing) |
| 1<<63 | 'true' | Large integer duplicate |
| -1 | 'true' | Negative value |
Sources: test/evidence/slt_lang_aggfunc.test:3-16 test/evidence/slt_lang_aggfunc.test:71-77 test/evidence/slt_lang_aggfunc.test:452-478
DISTINCT Keyword Support
All single-argument aggregate functions support the DISTINCT keyword, which filters duplicate values before aggregation.
DISTINCT Behavior Examples:
stateDiagram-v2
[*] --> ReadRow : Aggregate function execution
ReadRow --> CheckDistinct : Process row
CheckDistinct --> NormalAgg : No DISTINCT
CheckDistinct --> DistinctFilter : DISTINCT keyword present
DistinctFilter --> SeenBefore{"Value already\nseen?"}
SeenBefore -->|Yes| ReadRow : Skip row
SeenBefore -->|No| AddToSet : Add to seen set
AddToSet --> NormalAgg : Process value
NormalAgg --> Accumulate : Update aggregate
Accumulate --> ReadRow : Next row
ReadRow --> [*] : End of data
| Function | Without DISTINCT | With DISTINCT | Data |
|---|---|---|---|
count(x) | 4 | 3 | [1, 0, 2, 2] |
avg(x) | 1.25 | 1.0 | [1, 0, 2, 2] |
sum(x) | 5 | 3 | [1, 0, 2, 2] |
group_concat(x) | "1,0,2,2" | "1,0,2" | [1, 0, 2, 2] |
Evidence Reference: R-00466-56349 and R-00171-59428
Sources: test/evidence/slt_lang_aggfunc.test:24-82
NULL Handling Behavior
Each aggregate function handles NULL values differently. NULL values are filtered out before aggregation, but the return value when all inputs are NULL varies by function.
NULL Processing Matrix:
| Function | NULL Input Handling | All-NULL Result | Mixed NULL/Non-NULL Result |
|---|---|---|---|
count(x) | Excluded from count | 0 | Count of non-NULL values |
count(*) | N/A (counts rows) | 0 (if no rows) | Count of all rows |
avg(x) | Excluded from average | NULL | Average of non-NULL values |
sum(x) | Excluded from sum | NULL | Sum of non-NULL values |
total(x) | Excluded from sum | 0.0 | Sum of non-NULL values |
min(x) | Excluded from min | NULL | Minimum non-NULL value |
max(x) | Excluded from max | NULL | Maximum non-NULL value |
group_concat(x) | Excluded from concat | NULL | Concatenation of non-NULL |
Test Examples:
The test validates NULL handling by querying rows where y='null', which contains only a NULL value for x:
SELECT avg(x) FROM t1 WHERE y='null'→ NULLSELECT sum(x) FROM t1 WHERE y='null'→ NULLSELECT total(x) FROM t1 WHERE y='null'→ 0SELECT count(x) FROM t1 WHERE y='null'→ 0
Sources: test/evidence/slt_lang_aggfunc.test:184-196 test/evidence/slt_lang_aggfunc.test:396-418
Type Coercion and String Handling
Aggregate functions interpret non-numeric strings as 0 when performing arithmetic operations.
String Coercion Test Results:
flowchart LR
Input["Input Value"]
CheckType{"Value Type"}
IsNumber["Use numeric value"]
IsString["Check if looks\nlike number"]
IsNull["Exclude from\naggregation"]
LooksNumeric{"Parseable\nas number?"}
UseZero["Interpret as 0"]
ParseValue["Parse to number"]
Input --> CheckType
CheckType -->|INTEGER/REAL| IsNumber
CheckType -->|TEXT/BLOB| IsString
CheckType -->|NULL| IsNull
IsString --> LooksNumeric
LooksNumeric -->|No| UseZero
LooksNumeric -->|Yes| ParseValue
Testing with y VARCHAR(8) column containing values: 'true', 'false', 'NULL'
| Function | Result | Explanation |
|---|---|---|
count(y) | 5 | Counts non-NULL strings |
avg(y) | 0 | Strings interpreted as 0 |
sum(y) | 0 | All strings → 0, sum is 0 |
total(y) | 0 | All strings → 0, sum is 0.0 |
min(y) | 0 | Minimum of converted values |
max(y) | 0 | Maximum of converted values |
group_concat(y) | 'true,false,NULL,true,true' | Concatenates as strings |
Evidence Reference: R-29052-00975
Sources: test/evidence/slt_lang_aggfunc.test:92-166
graph TD
AvgFunc["avg()"]
SumFunc["sum()"]
TotalFunc["total()"]
CountFunc["count()"]
OtherFunc["min() / max() /\ngroup_concat()"]
AvgAlways["Always REAL\n(floating point)"]
SumCheck{"All inputs\nare INTEGER?"}
SumInt["Return INTEGER"]
SumFloat["Return REAL"]
TotalAlways["Always REAL"]
CountAlways["Always INTEGER"]
OtherMatch["Return type matches\ninput column type"]
AvgFunc --> AvgAlways
SumFunc --> SumCheck
SumCheck -->|Yes| SumInt
SumCheck -->|No| SumFloat
TotalFunc --> TotalAlways
CountFunc --> CountAlways
OtherFunc --> OtherMatch
style AvgAlways fill:#f9f9f9
style SumInt fill:#f9f9f9
style SumFloat fill:#f9f9f9
style TotalAlways fill:#f9f9f9
style CountAlways fill:#f9f9f9
style OtherMatch fill:#f9f9f9
Return Type Behavior
Aggregate functions return different types depending on the input data and function semantics.
Return Type Rules:
Type Coercion Examples:
| Query | Input Data | Expected Result | Result Type |
|---|---|---|---|
SELECT avg(x) FROM t1 | [1, 0, 2, 2] (all INT) | 1.250 | REAL (R) |
SELECT sum(x) FROM t1 | [1, 0, 2, 2] (all INT) | 5 | INTEGER (I) |
SELECT sum(x) FROM t1 | [1, 0, 4.0, 2, 2] (mixed) | 9.000 | REAL (R) |
SELECT total(x) FROM t1 | [1, 0, 2, 2] (all INT) | 5.000 | REAL (R) |
Evidence References:
- R-17177-10067 (avg always returns REAL)
- R-19660-56479 (sum returns INTEGER for all-integer input)
- R-33611-59266 (sum returns REAL if any non-integer input)
- R-07734-01023 (total always returns REAL)
Sources: test/evidence/slt_lang_aggfunc.test:168-182 test/evidence/slt_lang_aggfunc.test:420-464
Aggregate Functions Reference
count()
Syntax: count(x) or count(DISTINCT x) or count(*)
Behavior:
count(x): Returns the number of non-NULL values in column xcount(*): Returns the total number of rows in the groupcount(DISTINCT x): Returns the number of distinct non-NULL values
Return Type: INTEGER
NULL Handling: NULLs are excluded; returns 0 if no non-NULL values
Special Cases:
count(DISTINCT *)is a syntax errorcount(*)counts all rows, including those with all NULL columns
Test Coverage: test/evidence/slt_lang_aggfunc.test:28-31 test/evidence/slt_lang_aggfunc.test:198-234
Evidence References: R-34280-42283, R-13776-21310
avg()
Syntax: avg(x) or avg(DISTINCT x)
Behavior:
- Returns the average of all non-NULL values
- Always returns a floating-point value, even for integer inputs
Return Type: REAL (always)
NULL Handling: Returns NULL if all values are NULL
Type Coercion: Non-numeric strings interpreted as 0
Test Coverage: test/evidence/slt_lang_aggfunc.test:33-36 test/evidence/slt_lang_aggfunc.test:83-90 test/evidence/slt_lang_aggfunc.test:168-182
Evidence References: R-20409-33051, R-17177-10067, R-40597-22164
sum()
Syntax: sum(x) or sum(DISTINCT x)
Behavior:
- Returns the sum of all non-NULL values
- Return type depends on input: INTEGER if all inputs are integers, REAL otherwise
- Throws "integer overflow" exception on overflow with all-integer input
Return Type: INTEGER (all-integer input) or REAL (mixed input)
NULL Handling: Returns NULL if all values are NULL
Overflow Behavior: Integer overflow causes exception; test demonstrates with 1<<63 values
Test Coverage: test/evidence/slt_lang_aggfunc.test:38-41 test/evidence/slt_lang_aggfunc.test:375-394 test/evidence/slt_lang_aggfunc.test:434-464
Evidence References: R-24943-34514, R-19660-56479, R-33611-59266
total()
Syntax: total(x) or total(DISTINCT x)
Behavior:
- Similar to
sum()but always returns REAL - Returns 0.0 instead of NULL when all values are NULL
- Never throws integer overflow exception
Return Type: REAL (always)
NULL Handling: Returns 0.0 if all values are NULL
Comparison with sum():
| Scenario | sum() | total() |
|---|---|---|
| All NULL | NULL | 0.0 |
| All integers | INTEGER | REAL |
| Integer overflow | Exception | No exception |
Test Coverage: test/evidence/slt_lang_aggfunc.test:43-46 test/evidence/slt_lang_aggfunc.test:420-432 test/evidence/slt_lang_aggfunc.test:489-499
Evidence References: R-24943-34514, R-44223-43966, R-07734-01023
min()
Syntax: min(x) or min(DISTINCT x)
Behavior:
- Returns the minimum non-NULL value
- "Minimum" is the first value that would appear in
ORDER BY x - Uses column's collation for TEXT comparisons
Return Type: Matches input column type
NULL Handling: Returns NULL if all values are NULL
DISTINCT Effect: No practical difference (min is min regardless of duplicates)
Test Coverage: test/evidence/slt_lang_aggfunc.test:48-51 test/evidence/slt_lang_aggfunc.test:325-370
Evidence References: R-16028-39081, R-30311-39793, R-10396-30188
max()
Syntax: max(x) or max(DISTINCT x)
Behavior:
- Returns the maximum non-NULL value
- "Maximum" is the last value that would appear in
ORDER BY x - Uses column's collation for TEXT comparisons
Return Type: Matches input column type
NULL Handling: Returns NULL if all values are NULL
DISTINCT Effect: No practical difference (max is max regardless of duplicates)
Test Coverage: test/evidence/slt_lang_aggfunc.test:53-56 test/evidence/slt_lang_aggfunc.test:277-323
Evidence References: R-52585-35928, R-13053-11096, R-50775-16353
group_concat()
Syntax: group_concat(x) or group_concat(x, sep) or group_concat(DISTINCT x)
Behavior:
- Concatenates all non-NULL values into a single string
- Default separator is comma (",")
- Optional second parameter specifies custom separator
- DISTINCT can only be used with single-parameter form
Return Type: TEXT
NULL Handling:
- NULL values are excluded from concatenation
- Returns NULL if all values are NULL
Separator Rules:
- Default: ","
- Custom: second parameter (e.g.,
group_concat(x, ':')) - DISTINCT with custom separator is syntax error
Examples:
| Query | Input [1,0,2,2] | Result |
|---|---|---|
group_concat(x) | [1, 0, 2, 2] | "1,0,2,2" |
group_concat(DISTINCT x) | [1, 0, 2, 2] | "1,0,2" |
group_concat(x, ':') | [1, 0, 2, 2] | "1:0:2:2" |
group_concat(DISTINCT x, ':') | [1, 0, 2, 2] | Syntax error |
Test Coverage: test/evidence/slt_lang_aggfunc.test:58-62 test/evidence/slt_lang_aggfunc.test:236-275
Evidence References: R-56088-25150, R-08600-21007, R-39910-14723
Sources: test/evidence/slt_lang_aggfunc.test:1-500
flowchart TD
LargeInt["Large Integer Input\n(1<<63)"]
SumFunc["sum()
function"]
TotalFunc["total()
function"]
AllInt{"All inputs\nare INTEGER?"}
Overflow["Integer Overflow\nException Thrown"]
FloatResult["Convert to REAL\nReturn approximation"]
NoException["No Exception\nReturn REAL result"]
LargeInt --> SumFunc
LargeInt --> TotalFunc
SumFunc --> AllInt
AllInt -->|Yes| Overflow
AllInt -->|No| FloatResult
TotalFunc --> NoException
Integer Overflow Handling
The test suite includes specific validation for integer overflow behavior when using large values.
Overflow Test Data:
- Inserts
1<<63(9223372036854775808) twice - Inserts
-1once - Expected overflow when summing
Test Results:
| Function | Input Type | Result Behavior |
|---|---|---|
sum(x) | All INTEGER | Exception (empty result) |
sum(DISTINCT x) | All INTEGER | Converts to REAL: -9.223...e18 |
total(x) | All INTEGER | No exception: -1.844...e19 |
total(DISTINCT x) | All INTEGER | No exception: -9.223...e18 |
Evidence References:
- R-08904-24719 (sum() throws integer overflow)
- R-19553-64528 (total() never throws overflow)
Sources: test/evidence/slt_lang_aggfunc.test:466-499
Test Execution Directives
The aggregate function test file uses specific directives to control test execution and result comparison.
Key Directives Used:
| Directive | Example | Purpose |
|---|---|---|
statement ok | CREATE TABLE t1(...) | Execute statement, expect success |
query I nosort | Integer result, no sorting | Query returning integer values |
query R nosort | Real result, no sorting | Query returning floating-point values |
query T nosort | Text result, no sorting | Query returning text values |
label-NULL | Special NULL indicator | Label for expected NULL result |
label-sum | Descriptive label | Label for sum result queries |
skipif sqlite | Platform control | Skip on SQLite |
halt | Stop execution | Halt test execution |
statement error | Expected error | Expect statement to fail |
NOT INDEXED | Query hint | Force table scan, avoid index |
Platform Restriction:
The file begins with a platform check that halts execution on non-SQLite databases:
skipif sqlite
halt
This inverse logic (skip if SQLite, then halt) means: "If this is NOT SQLite, halt execution."
Sources: test/evidence/slt_lang_aggfunc.test:1-500
Evidence-Based Testing
The test file validates specific SQLite documentation requirements. Each test section includes comments with "EVIDENCE-OF" markers.
Evidence Reference Format:
Each comment references a specific requirement from SQLite's documentation:
- Format:
# EVIDENCE-OF: R-XXXXX-XXXXX <description> - The R-number is a unique identifier for that requirement
Evidence Coverage Map:
| Evidence ID | Requirement | Test Lines |
|---|---|---|
| R-00466-56349 | DISTINCT keyword support in aggregates | 24-31 |
| R-00171-59428 | DISTINCT filters duplicates before aggregation | 64-82 |
| R-31453-41389 | count(DISTINCT X) returns distinct count | 64-82 |
| R-20409-33051 | avg() returns average of non-NULL values | 83-90 |
| R-29052-00975 | Non-numeric strings interpreted as 0 | 92-166 |
| R-17177-10067 | avg() always returns floating point | 168-182 |
| R-40597-22164 | avg() returns NULL if all NULL | 184-196 |
| R-34280-42283 | count(X) counts non-NULL values | 198-220 |
| R-13776-21310 | count(*) counts all rows | 222-234 |
| R-56088-25150 | group_concat() concatenates non-NULL | 236-248 |
| R-08600-21007 | group_concat() custom separator | 250-261 |
| R-39910-14723 | group_concat() default comma separator | 263-275 |
| R-52585-35928 | max() returns maximum value | 277-289 |
| R-13053-11096 | max() uses ORDER BY ordering | 291-309 |
| R-50775-16353 | max() returns NULL if all NULL | 311-323 |
| R-16028-39081 | min() returns minimum non-NULL | 325-337 |
| R-30311-39793 | min() uses ORDER BY ordering | 339-356 |
| R-10396-30188 | min() returns NULL if all NULL | 358-370 |
| R-24943-34514 | sum()/total() return sum of non-NULL | 372-394 |
| R-44223-43966 | sum() NULL vs total() 0 for all-NULL | 396-418 |
| R-07734-01023 | total() always returns floating point | 420-432 |
| R-19660-56479 | sum() returns integer for all-integer | 434-446 |
| R-33611-59266 | sum() returns float for mixed input | 448-464 |
| R-08904-24719 | sum() throws overflow exception | 466-488 |
| R-19553-64528 | total() never throws overflow | 489-499 |
Sources: test/evidence/slt_lang_aggfunc.test:1-500
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Index Maintenance Tests
Relevant source files
Purpose and Scope
This document covers the test suite for index maintenance operations in SQLite, specifically focusing on the REINDEX command. These tests validate the functionality of rebuilding database indices from scratch, including error handling and cross-database compatibility considerations.
For general query operator tests, see Query Operator Tests. For comprehensive index optimization tests including BETWEEN operators, see Index and Optimization Tests.
Overview
The index maintenance test suite validates the REINDEX SQL command, which is a SQLite-specific extension not included in the SQL-92 standard. The primary test file test/evidence/slt_lang_reindex.test:1-59 contains evidence points that verify the command's specification and behavior.
The REINDEX command serves a single purpose: to delete and recreate indices from scratch. This functionality is critical for:
- Repairing potentially corrupted indices
- Updating indices after collation sequence changes
- Optimizing index structure after bulk data operations
Sources: test/evidence/slt_lang_reindex.test:1-59
Test Environment Setup
The test file establishes a minimal testing environment to validate REINDEX behavior:
Test Setup Sequence:
graph TB
subgraph "Test Database Schema"
T1["Table: t1\nColumns: x INTEGER, y VARCHAR(8)"]
I1["Index: t1i1\nON t1(x)"]
end
subgraph "Test Data"
R1["Row 1: (1, 'true')"]
R2["Row 2: (0, 'false')"]
R3["Row 3: (NULL, 'NULL')"]
end
T1 -->|indexed by| I1
T1 -->|contains| R1
T1 -->|contains| R2
T1 -->|contains| R3
| Step | Command | Line Reference |
|---|---|---|
| 1 | Create table t1 with columns x, y | test/evidence/slt_lang_reindex.test:3-4 |
| 2 | Insert row with x=1, y='true' | test/evidence/slt_lang_reindex.test:6-7 |
| 3 | Insert row with x=0, y='false' | test/evidence/slt_lang_reindex.test:9-10 |
| 4 | Insert row with x=NULL, y='NULL' | test/evidence/slt_lang_reindex.test:12-13 |
| 5 | Create index t1i1 on column x | test/evidence/slt_lang_reindex.test:15-16 |
Sources: test/evidence/slt_lang_reindex.test:3-16
Cross-Database Compatibility
Platform Support Matrix
The REINDEX command is not universally supported across database platforms. The test file uses conditional execution directives to handle platform-specific behavior:
| Database Platform | REINDEX Support | Alternative Command | Test Behavior |
|---|---|---|---|
| SQLite | ✓ Full support | N/A | All tests execute |
| PostgreSQL | ✓ Full support | N/A | All tests execute |
| MySQL | ✗ Not supported | REPAIR TABLE [tbl_name] | Tests halted via onlyif mysql halt |
| Microsoft SQL Server | ✗ Not supported | Platform-specific commands | Tests halted via onlyif mssql halt |
| Oracle | ✗ Not supported | Platform-specific commands | Tests halted via onlyif oracle halt |
Conditional Execution Flow
Halt Directives:
- test/evidence/slt_lang_reindex.test:22-24 - Halts execution for MSSQL
- test/evidence/slt_lang_reindex.test:26-28 - Halts execution for Oracle
- test/evidence/slt_lang_reindex.test:30-32 - Halts execution for MySQL
Sources: test/evidence/slt_lang_reindex.test:18-32
graph LR
CMD["REINDEX t1i1"]
IDX["Index: t1i1"]
DELETE["Delete Index"]
RECREATE["Recreate Index"]
RESULT["statement ok"]
CMD --> IDX
IDX --> DELETE
DELETE --> RECREATE
RECREATE --> RESULT
Command Syntax Validation
The test suite validates both successful and error cases for the REINDEX command:
Valid REINDEX Command
The test at test/evidence/slt_lang_reindex.test:37-38 executes REINDEX t1i1 and expects successful completion (statement ok). This validates that an existing index can be rebuilt.
Error Handling
The test at test/evidence/slt_lang_reindex.test:40-41 validates error handling by attempting to reindex a non-existent index tXiX. The expected behavior is statement error, confirming that the database properly rejects invalid index names.
Sources: test/evidence/slt_lang_reindex.test:37-41
Evidence Points Coverage
The test file documents specific evidence points from SQLite's specification, though some remain unimplemented (marked as TBD):
graph TB
EV1["R-52173-44778:\nREINDEX deletes and\nrecreates indices"]
T1["Test: REINDEX t1i1\n(line 38)"]
T2["Test: REINDEX tXiX\n(line 40-41)"]
EV1 --> T1
EV1 --> T2
T1 --> V1["Validates successful\nindex rebuild"]
T2 --> V2["Validates error handling\nfor invalid index"]
Implemented Evidence
| Evidence Point | Description | Status | Line Reference |
|---|---|---|---|
| R-52173-44778 | REINDEX command deletes and recreates indices from scratch | ✓ Tested | test/evidence/slt_lang_reindex.test:34-38 |
| R-38396-20088 | Reindexing all indices using named collation sequence | TBD | test/evidence/slt_lang_reindex.test:43-45 |
| R-46980-03026 | Reindexing all indices associated with a table | TBD | test/evidence/slt_lang_reindex.test:47-49 |
| R-50401-40957 | Reindexing a specific named index | TBD | test/evidence/slt_lang_reindex.test:51-52 |
| R-59524-35239 | Name resolution priority (collation vs table/index) | TBD | test/evidence/slt_lang_reindex.test:54-57 |
Pending Test Coverage
Four evidence points are marked as "TBD" (To Be Determined), indicating planned but not yet implemented test cases:
- Collation-based reindexing - Testing
REINDEXwith a collation sequence name to rebuild all indices using that collation - Table-based reindexing - Testing
REINDEXwith a table name to rebuild all indices on that table - Specific index reindexing - Already partially covered by existing tests, but may need additional validation
- Name resolution - Testing disambiguation when a name matches both a collation sequence and a table/index
Sources: test/evidence/slt_lang_reindex.test:34-57
Test File Metadata
The test file begins with a hash-threshold configuration:
hash-threshold 8
This directive at test/evidence/slt_lang_reindex.test1 sets the hash threshold parameter for the test execution environment. This controls when the query engine switches between different algorithms for query processing, though the specific implications are runtime-dependent.
Sources: test/evidence/slt_lang_reindex.test1
graph TB
subgraph "SQL Language Evidence Tests (3.1)"
DDL["DDL Tests (3.1.1)"]
DML["DML Tests (3.1.4)"]
QUERY["Query Tests (3.1.5)"]
REINDEX["Index Maintenance (3.1.8)"]
end
subgraph "REINDEX Test Components"
SETUP["Table and Index Creation\n(DDL operations)"]
DATA["Data Insertion\n(DML operations)"]
MAINT["Index Rebuilding\n(REINDEX operation)"]
end
DDL -.->|uses| SETUP
DML -.->|uses| DATA
REINDEX --> SETUP
REINDEX --> DATA
REINDEX --> MAINT
Integration with Test Corpus
This test file is part of the broader evidence test suite that validates SQL language compliance:
The REINDEX tests leverage DDL operations (table and index creation) and DML operations (data insertion) to establish a testable environment, then validate the index maintenance functionality.
Sources: test/evidence/slt_lang_reindex.test:1-59
Summary
The index maintenance test suite provides focused validation of the REINDEX command with the following characteristics:
| Aspect | Details |
|---|---|
| Test File | test/evidence/slt_lang_reindex.test |
| Total Lines | 59 |
| Platform Support | SQLite, PostgreSQL |
| Excluded Platforms | MySQL, MSSQL, Oracle |
| Evidence Points | 5 total (1 implemented, 4 pending) |
| Test Cases | 2 (1 success case, 1 error case) |
The test suite demonstrates the corpus's approach to handling non-standard SQL extensions: documenting platform compatibility constraints using conditional directives, validating core functionality where supported, and marking future test coverage areas for comprehensive specification validation.
For information on how conditional execution directives work across the entire corpus, see Conditional Execution Directives. For platform-specific behavior documentation, see Platform-Specific Behaviors.
Sources: test/evidence/slt_lang_reindex.test:1-59
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
SQL Language Evidence Tests
Relevant source files
- test/evidence/slt_lang_createtrigger.test
- test/evidence/slt_lang_createview.test
- test/evidence/slt_lang_update.test
Purpose and Scope
SQL Language Evidence Tests validate that SQLite's implementation conforms to its documented SQL language specification. These tests verify behavior for Data Definition Language (DDL), Data Manipulation Language (DML), and Data Query Language (DQL) commands by executing SQL statements and comparing results against expected outcomes. Each test includes citations to SQLite's official documentation using evidence markers (e.g., EVIDENCE-OF: R-xxxxx-xxxxx), establishing traceability between test cases and specification requirements.
This page provides an overview of the evidence test system. For detailed coverage of specific command categories, see:
For tests focused on query optimization and index behavior, see Index and Optimization Tests #3.2.
Evidence Test Organization
Evidence tests are located in the test/evidence/ directory and follow the naming convention slt_lang_<command>.test. Each file contains comprehensive test cases for a specific SQL language feature, organized with evidence citations that map to SQLite's documentation.
Evidence Test Files by Category
| Category | Test Files | SQL Commands Covered |
|---|---|---|
| DDL - CREATE | slt_lang_createtrigger.test | |
slt_lang_createview.test | CREATE TRIGGER | |
| CREATE VIEW (including TEMP/TEMPORARY) | ||
| DDL - DROP | slt_lang_dropindex.test | |
slt_lang_droptable.test | ||
slt_lang_droptrigger.test | ||
slt_lang_dropview.test | DROP INDEX | |
| DROP TABLE | ||
| DROP TRIGGER | ||
| DROP VIEW | ||
| DDL - Maintenance | slt_lang_reindex.test | REINDEX |
| DML | slt_lang_update.test | |
slt_lang_replace.test | UPDATE | |
| REPLACE / INSERT OR REPLACE | ||
| DQL - Operators | in1.test | |
in2.test | IN / NOT IN operators | |
| DQL - Functions | slt_lang_aggfunc.test | Aggregate functions (COUNT, AVG, SUM, MIN, MAX, GROUP_CONCAT) |
Sources: test/evidence/slt_lang_createtrigger.test:1-220 test/evidence/slt_lang_createview.test:1-129 test/evidence/slt_lang_update.test:1-198
Evidence Citation System
Evidence Marker Taxonomy
Evidence Marker Format: Each marker follows the pattern R-{5-digit}-{5-digit} where the numbers serve as unique identifiers in SQLite's documentation system. Test files use comment syntax to associate evidence markers with test cases.
Sources: test/evidence/slt_lang_createtrigger.test:21-22 test/evidence/slt_lang_createtrigger.test:31-32 test/evidence/slt_lang_createview.test:42-45 test/evidence/slt_lang_createview.test:87-88
Evidence-to-Test Mapping Pattern
Evidence tests follow a consistent pattern where documentation citations precede the corresponding test directives:
# EVIDENCE-OF: R-xxxxx-xxxxx <Documentation excerpt>
statement ok
<SQL command to validate the documented behavior>
Example from CREATE TRIGGER tests:
Sources: test/evidence/slt_lang_createtrigger.test:21-25
Test Execution Flow for Evidence Tests
The execution model processes test files sequentially, evaluating platform directives before each test block. Evidence citations are parsed as documentation but do not affect test execution—they serve as traceability markers linking test cases to specification requirements.
Sources: test/evidence/slt_lang_createtrigger.test:1-16 test/evidence/slt_lang_createtrigger.test:18-19 test/evidence/slt_lang_createview.test:68-86
Common Test Schema Initialization Pattern
Evidence tests establish a consistent baseline schema used across multiple test files:
| Operation | SQL Command | Purpose |
|---|---|---|
| Create table | CREATE TABLE t1(x INTEGER, y VARCHAR(8)) | Base table for testing |
| Insert test data | INSERT INTO t1 VALUES(1,'true') | True condition test case |
INSERT INTO t1 VALUES(0,'false') | False condition test case | |
INSERT INTO t1 VALUES(NULL,'NULL') | NULL handling test case | |
| Create index | CREATE INDEX t1i1 ON t1(x) | Enable indexed operation tests |
This schema pattern appears in test/evidence/slt_lang_createtrigger.test:3-16 test/evidence/slt_lang_createview.test:3-16 and test/evidence/slt_lang_update.test:3-16 providing a consistent testing foundation across evidence test files.
Sources: test/evidence/slt_lang_createtrigger.test:3-16 test/evidence/slt_lang_createview.test:3-16 test/evidence/slt_lang_update.test:3-16
DDL Command Coverage
CREATE Statement Evidence Tests
Evidence tests for CREATE commands validate object creation semantics, error handling for duplicate objects, and platform-specific variations:
MSSQL Platform Behavior: The CREATE TRIGGER tests include onlyif mssql followed by halt at test/evidence/slt_lang_createtrigger.test:18-19 indicating that MSSQL does not support the full trigger syntax used in subsequent tests. Views in MSSQL allow UPDATE and DELETE operations (test/evidence/slt_lang_createview.test:68-85), diverging from SQLite's read-only view behavior.
graph LR
subgraph "CREATE TRIGGER Tests"
CT_FILE["slt_lang_createtrigger.test"]
CT_BASIC["Basic trigger creation\nstatement ok at line 25"]
CT_DUP["Duplicate detection\nstatement error at line 28-29"]
CT_TIMING["BEFORE/AFTER timing\nlines 81-96"]
CT_EVENTS["INSERT/UPDATE/DELETE events\nlines 40-46"]
CT_FILE --> CT_BASIC
CT_FILE --> CT_DUP
CT_FILE --> CT_TIMING
CT_FILE --> CT_EVENTS
end
subgraph "CREATE VIEW Tests"
CV_FILE["slt_lang_createview.test"]
CV_BASIC["Basic view creation\nstatement ok at line 23"]
CV_DUP["Duplicate detection\nstatement error at line 26-27"]
CV_TEMP["TEMP/TEMPORARY views\nlines 48-53"]
CV_READONLY["Read-only enforcement\nlines 68-103"]
CV_FILE --> CV_BASIC
CV_FILE --> CV_DUP
CV_FILE --> CV_TEMP
CV_FILE --> CV_READONLY
end
style CT_FILE fill:#f9f9f9
style CV_FILE fill:#f9f9f9
style CT_BASIC fill:#e8f5e9
style CV_BASIC fill:#e8f5e9
style CT_DUP fill:#ffebee
style CV_DUP fill:#ffebee
Sources: test/evidence/slt_lang_createtrigger.test:18-46 test/evidence/slt_lang_createview.test:23-53 test/evidence/slt_lang_createview.test:68-103
DROP Statement Evidence Tests
DROP statement tests verify object deletion, error handling for non-existent objects, and cleanup behavior:
| Test File | Object Type | Key Test Cases |
|---|---|---|
slt_lang_droptrigger.test | Triggers | Drop created triggers (lines 192-219 in createtrigger.test) |
slt_lang_dropview.test | Views | Drop view, error on already-dropped, error on non-existent |
slt_lang_dropindex.test | Indexes | Drop index validation |
slt_lang_droptable.test | Tables | Drop table validation |
The CREATE TRIGGER test file demonstrates the DROP pattern at test/evidence/slt_lang_createtrigger.test:192-219 where all created triggers are dropped sequentially. CREATE VIEW tests show error handling for DROP operations at test/evidence/slt_lang_createview.test:112-128
Sources: test/evidence/slt_lang_createtrigger.test:192-219 test/evidence/slt_lang_createview.test:112-128
DML Command Coverage
graph TB
subgraph "UPDATE Test Scenarios"
UPDATE_FILE["slt_lang_update.test"]
subgraph "Basic Operations"
UPDATE_WHERE["UPDATE with WHERE clause\nR-58095-46013\nlines 54-62"]
UPDATE_NOWHERE["UPDATE without WHERE\naffects all rows\nlines 42-51"]
UPDATE_EXPR["Scalar expressions\nR-40598-36595\nlines 71-81"]
end
subgraph "Column Assignment Rules"
UPDATE_MULTI["Multiple assignments\nrightmost wins\nR-34751-18293\nlines 84-107"]
UPDATE_UNMOD["Unmodified columns\nretain values\nR-40472-60438\nlines 109-123"]
UPDATE_SELF["Self-referential updates\nR-36239-04077\nlines 126-137"]
end
subgraph "Error Handling"
UPDATE_BADCOL["Invalid column error\nlines 36-40"]
UPDATE_EMPTY["Empty WHERE clause\naffects zero rows\nR-58129-20729\nlines 64-69"]
end
end
UPDATE_FILE --> UPDATE_WHERE
UPDATE_FILE --> UPDATE_NOWHERE
UPDATE_FILE --> UPDATE_EXPR
UPDATE_FILE --> UPDATE_MULTI
UPDATE_FILE --> UPDATE_UNMOD
UPDATE_FILE --> UPDATE_SELF
UPDATE_FILE --> UPDATE_BADCOL
UPDATE_FILE --> UPDATE_EMPTY
style UPDATE_FILE fill:#f9f9f9
style UPDATE_WHERE fill:#e8f5e9
style UPDATE_MULTI fill:#fff9c4
style UPDATE_SELF fill:#e1f5fe
UPDATE Statement Evidence Tests
UPDATE tests validate row modification behavior, WHERE clause evaluation, and column assignment semantics:
Multiple Assignment Behavior: Evidence marker R-34751-18293 at test/evidence/slt_lang_update.test:83-107 documents that when a column appears multiple times in the assignment list, only the rightmost assignment takes effect. This behavior is tested with UPDATE t1 SET x=3, x=4, x=5 where only x=5 is applied, but these tests are skipped on MSSQL via skipif mssql directives.
Self-Referential Updates: Evidence marker R-04558-24451 at test/evidence/slt_lang_update.test:129-137 validates that all scalar expressions are evaluated before any assignments occur, enabling updates like UPDATE t1 SET x=x+2 to work correctly.
Sources: test/evidence/slt_lang_update.test:19-137
REPLACE Statement Tests
The REPLACE command (INSERT OR REPLACE) tests are located in test/evidence/slt_lang_replace.test and validate SQLite's REPLACE syntax. This is a SQLite-specific extension that may not be available on all database platforms.
Sources: Referenced in file list but detailed content not provided
Cross-Platform Compatibility in Evidence Tests
Platform-Specific Directive Usage
Evidence tests include conditional directives to handle database platform differences:
| Directive | Database | Usage Pattern | Example Location |
|---|---|---|---|
onlyif mssql | |||
halt | MSSQL | Stop execution for incompatible features | createtrigger.test:18-19 |
skipif mssql | MSSQL | Skip tests that fail on MSSQL | createview.test:68-69 |
| update.test:87-107 | |||
onlyif mssql | MSSQL | Run platform-specific alternative | createview.test:72-85 |
onlyif sqlite | SQLite | Test SQLite-specific features | createview.test:47-53 |
| createview.test:89-103 |
Platform Behavior Matrix
| Feature | SQLite | MSSQL | Test Coverage |
|---|---|---|---|
| Triggers | Full support | Halted due to syntax differences | createtrigger.test:18-19 |
| Views - UPDATE/DELETE | Error (read-only) | Allowed | createview.test:68-85 |
| Temporary views | TEMP/TEMPORARY keywords | Not tested separately | createview.test:48-53 |
| Multiple column assignments | Rightmost wins | Different behavior | update.test:87-107 |
The MSSQL platform demonstrates significant SQL dialect differences, particularly in trigger syntax (causing complete test halt) and view mutability (allowing UPDATE/DELETE operations that SQLite prohibits).
Sources: test/evidence/slt_lang_createtrigger.test:18-19 test/evidence/slt_lang_createview.test:47-103 test/evidence/slt_lang_update.test:87-107
Evidence Test Execution Model
Evidence tests maintain traceability throughout execution by tracking which evidence markers are covered by test cases. This enables coverage reporting that maps test results back to specification requirements.
Sources: test/evidence/slt_lang_createtrigger.test:21-96 test/evidence/slt_lang_createview.test:19-128 test/evidence/slt_lang_update.test:19-198
Test File Structure Pattern
All evidence test files follow a consistent structure:
- Hash threshold declaration:
hash-threshold 8at file start - Schema initialization: CREATE TABLE, INSERT, CREATE INDEX (lines 3-16)
- Platform compatibility checks:
onlyif,skipif,haltdirectives - Evidence-annotated test blocks: Evidence citations followed by test directives
- Cleanup operations: DROP statements to remove created objects
This pattern is consistent across test/evidence/slt_lang_createtrigger.test:1-220 test/evidence/slt_lang_createview.test:1-129 and test/evidence/slt_lang_update.test:1-198 facilitating automated test parsing and execution.
Sources: test/evidence/slt_lang_createtrigger.test:1-220 test/evidence/slt_lang_createview.test:1-129 test/evidence/slt_lang_update.test:1-198
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
BETWEEN Operator Test Suites
Relevant source files
- test/index/between/1/slt_good_0.test
- test/index/between/10/slt_good_0.test
- test/index/between/10/slt_good_1.test
- test/index/between/10/slt_good_2.test
- test/index/between/10/slt_good_3.test
- test/index/between/10/slt_good_4.test
- test/index/between/10/slt_good_5.test
- test/index/between/100/slt_good_0.test
Purpose and Scope
This section documents the comprehensive test suites for the SQL BETWEEN operator, focusing on query optimization behavior with various index configurations. These tests validate that the BETWEEN operator produces identical results to its equivalent >= AND <= expression across different data types, index strategies, and table sizes.
For general information about index and optimization tests, see Index and Optimization Tests. For SQL language evidence tests, see SQL Language Evidence Tests.
Test Suite Organization
The BETWEEN operator test suites are organized by data volume, with each category testing identical query patterns against varying dataset sizes:
| Directory | Dataset Size | Test Files | Purpose |
|---|---|---|---|
test/index/between/1/ | 1 row per table | slt_good_0.test | Minimal data edge cases |
test/index/between/10/ | 10 rows per table | slt_good_0.test through slt_good_5.test | Standard query optimization scenarios |
test/index/between/100/ | 100 rows per table | slt_good_0.test | Large dataset performance validation |
Sources: test/index/between/1/slt_good_0.test:1-4 test/index/between/10/slt_good_0.test:1-4
Directory Structure
Sources: test/index/between/1/slt_good_0.test1 test/index/between/10/slt_good_0.test1
Table Schema and Index Configurations
Each test file creates five tables (tab0 through tab4) with identical schemas but different index configurations to validate query optimizer behavior across various index strategies.
Common Table Schema
All tables share the following structure:
Sources: test/index/between/1/slt_good_0.test:3-4 test/index/between/10/slt_good_0.test:3-4
Index Configuration Matrix
Sources: test/index/between/1/slt_good_0.test:13-23 test/index/between/10/slt_good_0.test:39-50
Index Configuration Examples
The following table shows representative index configurations from test/index/between/10/slt_good_0.test:
| Table | Index Name | Columns | Attributes |
|---|---|---|---|
tab0 | (none) | - | Baseline for comparison |
tab1 | idx_tab1_0 | col0 | Single column |
tab1 | idx_tab1_1 | col1 | Single column |
tab1 | idx_tab1_3 | col3 | Single column |
tab1 | idx_tab1_4 | col4 | Single column |
tab2 | idx_tab2_0 | col1, col0 | Composite |
tab2 | idx_tab2_1 | col4 | UNIQUE |
tab2 | idx_tab2_4 | col1 DESC, col3 DESC | Composite, descending |
tab3 | idx_tab3_0 | col3 | UNIQUE |
tab3 | idx_tab3_1 | col0 DESC | UNIQUE, descending |
tab3 | idx_tab3_3 | col4 | Single column |
tab4 | idx_tab4_1 | col3, col1 | UNIQUE, composite |
tab4 | idx_tab4_2 | col4 | Single column |
tab4 | idx_tab4_4 | col1 DESC, col3 | UNIQUE, composite |
tab4 | idx_tab4_5 | col0 | UNIQUE |
Sources: test/index/between/10/slt_good_0.test:39-98
Test Methodology
Equivalence Validation Pattern
Each test validates that BETWEEN and its equivalent >= AND <= expression produce identical results. This is critical for ensuring query optimizer correctness.
Sources: test/index/between/1/slt_good_0.test:69-84 test/index/between/10/slt_good_0.test:102-150
sequenceDiagram
participant Test as Test Harness
participant DB as Database Engine
Note over Test: For each table (tab0-tab4)
Test->>DB: Execute query with BETWEEN
DB-->>Test: Result set A (with hash)
Test->>DB: Execute query with >= AND <=
DB-->>Test: Result set B (with hash)
Test->>Test: Compare Result A == Result B
alt Results match
Test->>Test: Test passes
else Results differ
Test->>Test: Test fails
end
Query Pair Structure
Every BETWEEN test consists of paired queries using labels for result comparison:
Key components:
query I: Expects integer result typerowsort: Results are order-independent (sorted for comparison)label-0: Shared label ensures both queries produce identical results- Both queries executed on all 5 tables for cross-index validation
Sources: test/index/between/1/slt_good_0.test:69-76
BETWEEN Operator Test Scenarios
1. Simple Range Queries
Basic BETWEEN operations on indexed and non-indexed columns:
Sources: test/index/between/10/slt_good_0.test:193-270
2. Inverted Range Edge Cases
Tests where the lower bound exceeds the upper bound (should return empty result set):
Sources: test/index/between/1/slt_good_0.test:209-247
3. BETWEEN in Complex Predicates
BETWEEN combined with multiple logical operators:
Sources: test/index/between/1/slt_good_0.test:69-107 test/index/between/1/slt_good_0.test:109-157
4. BETWEEN with NULL Handling
Tests involving NULL values and BETWEEN operations:
Sources: test/index/between/1/slt_good_0.test:159-207
5. Nested BETWEEN in Subqueries
BETWEEN within IN clauses and subquery predicates:
Sources: test/index/between/10/slt_good_0.test:152-190
6. Multi-Column BETWEEN Conditions
Multiple BETWEEN clauses on different columns:
Sources: test/index/between/10/slt_good_0.test:513-511
Data Type Coverage
Integer Columns (col0, col3)
Tests cover:
- Positive integers
- Range boundaries (inclusive)
- Single-value ranges (
BETWEEN x AND x) - Inverted ranges
- NULL comparisons
Sources: test/index/between/10/slt_good_0.test:193-270
Float Columns (col1, col4)
Tests cover:
- Decimal precision
- Scientific notation boundaries
- Fractional range queries
- Mixed precision comparisons
Sources: test/index/between/10/slt_good_0.test:272-350
Cross-Index Validation Strategy
Purpose: Validates that the query optimizer produces correct results regardless of available indexes, ensuring BETWEEN operations are handled consistently across different access paths.
Sources: test/index/between/1/slt_good_0.test:69-107
Test Label Organization
Tests use numeric labels to group equivalent queries:
| Label Pattern | Query Count | Purpose |
|---|---|---|
label-0 | 2 per table × 5 tables | First test case in file |
label-10 | 2 per table × 5 tables | Second test case in file |
label-20 | 2 per table × 5 tables | Third test case in file |
| ... | ... | Continues in increments of 10 |
Each label group contains:
- Query with
BETWEENoperator - Query with
>= AND <=equivalent - Both executed on all 5 tables (10 total queries per label)
Sources: test/index/between/1/slt_good_0.test69 test/index/between/1/slt_good_0.test109
Hash Threshold Configuration
All test files begin with:
hash-threshold 8
This directive instructs the test harness to:
- Hash result sets with more than 8 rows for comparison
- Display individual row values for result sets ≤ 8 rows
- Enable efficient comparison of large result sets
Sources: test/index/between/1/slt_good_0.test1 test/index/between/10/slt_good_0.test1
Data Population Strategy
Single Row Tests (between/1/)
Each table contains exactly one row with identical data:
All tables (tab0 through tab4) populated via:
Purpose: Isolates BETWEEN operator logic from result set size variations, focusing on edge case behavior.
Sources: test/index/between/1/slt_good_0.test:6-7 test/index/between/1/slt_good_0.test25
Ten Row Tests (between/10/)
Each test file has unique data but follows the same pattern:
Tables tab1 through tab4 populated identically from tab0.
Purpose: Tests query optimization with realistic data volumes and distribution.
Sources: test/index/between/10/slt_good_0.test:6-34 test/index/between/10/slt_good_0.test52
Result Comparison Methodology
Order-Independent Comparison
All queries use rowsort to ensure deterministic comparison:
query I rowsort label-N
- Results are sorted before comparison
- Hash values computed for large result sets
- Label matching ensures BETWEEN and equivalent queries match
Expected Result Format
Both queries must produce:
- Identical row values
- Identical row count
- Identical hash (if > 8 rows)
Sources: test/index/between/1/slt_good_0.test:69-76
Test Coverage Summary
By Data Volume
| Dataset Size | Primary Focus | Test Files |
|---|---|---|
| 1 row | Edge cases, NULL handling, empty results | 1 file |
| 10 rows | Query optimization, index selection | 6 files |
| 100 rows | Performance, large result sets | 1 file |
By Index Type
| Index Type | Tables | Validation Focus |
|---|---|---|
| No indexes | tab0 | Baseline correctness |
| Single column | tab1 | Basic index utilization |
| Composite | tab2, tab3 | Multi-column access paths |
| UNIQUE | tab2, tab3, tab4 | Constraint interaction |
| DESC ordering | tab2, tab3, tab4 | Sort order optimization |
By Operator Complexity
| Scenario | Example | Test Count |
|---|---|---|
| Simple BETWEEN | col BETWEEN a AND b | High |
| Inverted ranges | col BETWEEN b AND a (b > a) | Medium |
| Complex predicates | BETWEEN with AND/OR/NOT | High |
| Nested expressions | BETWEEN in subqueries | Medium |
| NULL handling | BETWEEN with IS NULL | Low |
Sources: All test files in test/index/between/
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Index and Optimization Tests
Relevant source files
- test/index/between/1/slt_good_0.test
- test/index/between/10/slt_good_0.test
- test/index/between/10/slt_good_1.test
- test/index/between/10/slt_good_2.test
- test/index/between/10/slt_good_3.test
- test/index/between/10/slt_good_4.test
- test/index/between/10/slt_good_5.test
- test/index/between/100/slt_good_0.test
Purpose and Scope
Index and Optimization Tests validate that SQLite's query optimizer correctly utilizes indexes when executing queries, particularly those involving the BETWEEN operator and complex WHERE clauses. These tests ensure that:
- Queries produce identical results regardless of index configuration
- The
BETWEENoperator is semantically equivalent to its expanded form (col >= lower AND col <= upper) - Query optimization works correctly across varying data volumes and complexity levels
For SQL language specification compliance tests covering DDL, DML, and DQL commands, see SQL Language Evidence Tests. For detailed coverage of BETWEEN operator test variations, see BETWEEN Operator Test Suites.
Sources: test/index/between/1/slt_good_0.test:1-108 test/index/between/10/slt_good_0.test:1-150
Test Organization Structure
Index tests are organized hierarchically by data volume, enabling validation across different dataset sizes that may trigger different optimization strategies.
Test Volume Characteristics:
graph TD
ROOT["test/index/"]
BETWEEN["between/"]
SET1["1/"]
SET10["10/"]
SET100["100/"]
FILE1_0["slt_good_0.test\n1 data row per table"]
FILE10_0["slt_good_0.test\n10 rows"]
FILE10_1["slt_good_1.test\n10 rows"]
FILE10_2["slt_good_2.test\n10 rows"]
FILE10_3["slt_good_3.test\n10 rows"]
FILE10_4["slt_good_4.test\n10 rows"]
FILE10_5["slt_good_5.test\n10 rows"]
FILE100_0["slt_good_0.test\n100+ rows"]
ROOT --> BETWEEN
BETWEEN --> SET1
BETWEEN --> SET10
BETWEEN --> SET100
SET1 --> FILE1_0
SET10 --> FILE10_0
SET10 --> FILE10_1
SET10 --> FILE10_2
SET10 --> FILE10_3
SET10 --> FILE10_4
SET10 --> FILE10_5
SET100 --> FILE100_0
| Directory | Rows Per Table | Test Files | Purpose |
|---|---|---|---|
between/1/ | 1 | 1 | Minimal data validation |
between/10/ | 10 | 6 | Standard regression suite |
between/100/ | 100+ | 1+ | Performance validation |
Sources: test/index/between/1/slt_good_0.test:1-10 test/index/between/10/slt_good_0.test:1-100
Table Schema and Index Configurations
Each test file creates five tables with identical schema but different index configurations. This design validates that query results remain consistent regardless of which indexes the optimizer chooses to use.
Standard Table Schema
All tables use this schema:
Index Configuration Matrix
Index Configuration Patterns:
| Table | Index Strategy | Key Characteristics |
|---|---|---|
tab0 | None | Baseline for full table scans |
tab1 | Single-column | Indexes on col0, col1, col3, col4 |
tab2 | Compound | Multi-column and descending indexes |
tab3 | Unique | UNIQUE constraints with compound keys |
tab4 | Mixed | Combination of unique and descending |
Sources: test/index/between/1/slt_good_0.test:3-67 test/index/between/10/slt_good_0.test:4-100
Test Methodology
Query Equivalence Validation
Each test validates that BETWEEN produces identical results to its expanded form. The test pattern repeats for each table configuration:
sequenceDiagram
participant TF as Test File
participant DB as Database
participant V as Validator
Note over TF,V: For each table (tab0..tab4)
TF->>DB: Execute query with BETWEEN
DB->>V: Result Set A
TF->>DB: Execute equivalent query with >= AND <=
DB->>V: Result Set B
V->>V: Compare Result Set A vs B
alt Results Match
V->>TF: PASS
else Results Differ
V->>TF: FAIL (optimizer bug)
end
Label-Based Test Groups
Tests use labels to group related queries:
query I rowsort label-0
SELECT pk FROM tab0 WHERE col3 < 6 OR col3 < 1 AND col3 BETWEEN 2 AND 7
----
query I rowsort label-0
SELECT pk FROM tab0 WHERE col3 < 6 OR col3 < 1 AND (col3 >= 2 AND col3 <= 7)
----
Each label-N represents a specific query pattern tested across all five table configurations, with both BETWEEN and expanded syntax.
Sources: test/index/between/1/slt_good_0.test:69-107 test/index/between/10/slt_good_0.test:102-150
Query Pattern Categories
Simple Range Queries
Basic BETWEEN operations on indexed columns:
Sources: test/index/between/1/slt_good_0.test:69-84
Complex Compound Predicates
Multi-condition queries combining BETWEEN with other operators:
Sources: test/index/between/1/slt_good_0.test:109-157
Nested Subquery Integration
BETWEEN within subquery predicates:
Sources: test/index/between/10/slt_good_0.test:152-190
NULL Handling with BETWEEN
Tests validate BETWEEN behavior with NULL values:
Sources: test/index/between/1/slt_good_0.test:159-207
Query Complexity Spectrum
Tests progressively increase in complexity to stress-test the optimizer:
Complexity Distribution:
| Label Range | Predicate Count | Query Features |
|---|---|---|
| label-0 to label-10 | 1-3 | Basic BETWEEN, simple OR/AND |
| label-20 to label-30 | 4-6 | Multiple columns, NULL checks |
| label-40 to label-50 | 7-10 | Nested conditions, IN clauses |
| label-60+ | 10+ | Deep nesting, subqueries, complex boolean logic |
Sources: test/index/between/1/slt_good_0.test:69-247
Result Validation Approach
Hash-Based Comparison
Tests use hash-threshold 8 to enable result set hashing for efficient comparison:
hash-threshold 8
query I rowsort label-0
SELECT pk FROM tab0 WHERE col3 > 23 AND ((col0 <= 96)) OR (col3 >= 39)
----
10 values hashing to e20b902b49a98b1a05ed62804c757f94
The hash value ensures:
- Result sets from different tables match exactly
BETWEENand expanded syntax produce identical results- Order-independent comparison via
rowsort
Sources: test/index/between/10/slt_good_0.test:1-110
Cross-Table Consistency Validation
Sources: test/index/between/1/slt_good_0.test:69-107
Data Type Coverage
Tests validate BETWEEN across all supported data types:
| Data Type | Column | Test Examples |
|---|---|---|
| INTEGER | col0, col3 | col0 BETWEEN 7 AND 4 |
| FLOAT | col1, col4 | col4 BETWEEN 6.51 AND 4.36 |
| TEXT | col2, col5 | Primarily in data setup, not BETWEEN predicates |
Inverted Range Testing:
Many tests intentionally use inverted ranges (e.g., BETWEEN 7 AND 4 where 7 > 4) to validate that:
- Empty result sets are handled correctly
- Optimizer doesn't incorrectly rewrite the predicate
- Semantic equivalence is maintained
Sources: test/index/between/1/slt_good_0.test:109-157 test/index/between/10/slt_good_0.test:272-350
Optimizer Coverage Areas
Index Selection Validation
Tests verify the optimizer correctly chooses indexes for BETWEEN predicates:
Sources: test/index/between/10/slt_good_2.test:199-247
Descending Index Utilization
Tests validate BETWEEN works with DESC indexes:
Sources: test/index/between/10/slt_good_0.test:58-70
Composite Index Prefix Usage
Validates optimizer can use compound index prefixes for BETWEEN:
Sources: test/index/between/10/slt_good_0.test:58-67
Test Execution Flow
Test File Structure:
-
Setup Phase:
hash-threshold 8directiveCREATE TABLEstatements for tab0-tab4INSERT INTOstatements (1, 10, or 100+ rows)CREATE INDEXstatements per table
-
Test Phase:
- Grouped by
label-Nidentifiers - Two queries per table per label (BETWEEN vs expanded)
- Expected results follow
----separator
- Grouped by
-
Validation:
- Hash-based result comparison
- Automatic failure on mismatch
Sources: test/index/between/1/slt_good_0.test:1-108 test/index/between/10/slt_good_0.test:1-150
Special Test Cases
Empty Result Sets
Tests validate correct handling of unsatisfiable conditions:
Sources: test/index/between/1/slt_good_0.test:159-167
Extreme Predicate Nesting
Label-50 and higher test deeply nested boolean expressions:
These tests ensure the optimizer handles complex expressions without incorrectly simplifying or rewriting predicates.
Sources: test/index/between/1/slt_good_0.test:289-337
Multiple BETWEEN Clauses
Tests validate queries with multiple BETWEEN predicates:
Sources: test/index/between/1/slt_good_0.test:289-296
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Cross-Database Compatibility
Relevant source files
- test/evidence/in1.test
- test/evidence/in2.test
- test/evidence/slt_lang_dropindex.test
- test/evidence/slt_lang_reindex.test
- test/evidence/slt_lang_replace.test
Purpose and Scope
This document explains how the sqllogictest corpus supports multiple database platforms through conditional test execution. The test files use directives (skipif, onlyif, halt) to control whether specific tests run on different SQL engines, allowing a single test corpus to validate behavior across SQLite, MySQL, MSSQL, Oracle, and PostgreSQL while accommodating platform-specific SQL implementation differences.
For information about the test file format and directive syntax, see Test File Format Specification. For details on how to integrate the corpus with CI/CD systems for multi-database testing, see Integrating with CI/CD Systems.
Cross-Database Testing Model
The corpus employs a conditional execution strategy where test directives control execution flow based on the target database platform. This allows the same test file to validate SQL behavior across different engines while handling platform-specific limitations and non-standard features.
Supported Database Platforms
The following table summarizes the database platforms supported by the test corpus and their key characteristics:
| Platform | Identifier | Primary Support Level | Notable Limitations |
|---|---|---|---|
| SQLite | sqlite | Full (primary target) | None - all tests run |
| MySQL | mysql | Partial | No empty RHS in IN, no REINDEX, UNIQUE index syntax differences |
| Microsoft SQL Server | mssql | Partial | Different DROP INDEX syntax, no REINDEX, different view semantics |
| Oracle | oracle | Partial | No REINDEX, VARCHAR vs TEXT differences, no empty RHS in IN |
| PostgreSQL | postgresql / postgres | High | Standard SQL compliance, supports REINDEX |
Sources: test/evidence/in1.test:1-10 test/evidence/slt_lang_reindex.test:18-32 test/evidence/slt_lang_replace.test:1-7
Conditional Execution Architecture
Execution Control Flow
Diagram: Conditional Execution State Machine
The test runner processes directives line-by-line to determine whether subsequent SQL statements should execute. The onlyif directive allows execution only on matching platforms, while skipif prevents execution on matching platforms. The halt directive immediately terminates file processing for the current platform.
Sources: test/evidence/in1.test:4-9 test/evidence/slt_lang_reindex.test:22-32
Platform-Specific Test Isolation Patterns
Pattern 1: Complete File Exclusion
Tests that rely on platform-specific features can exclude entire files using the halt directive immediately after platform detection:
# Skip this entire file if MS SQL Server
onlyif mssql
halt
# Skip this entire file if Oracle
onlyif oracle
halt
This pattern appears in tests for SQLite-specific features like the REINDEX command and REPLACE syntax.
Sources: test/evidence/slt_lang_reindex.test:22-28 test/evidence/slt_lang_replace.test:1-7 test/evidence/in1.test:3-9
Pattern 2: Selective Statement Skipping
For tests that are mostly portable but have specific incompatibilities, directives precede individual statements:
# Only run on SQLite (empty RHS not standard SQL)
onlyif sqlite
query I nosort
SELECT 1 IN ()
----
0
This allows a single test file to run on multiple platforms while skipping non-portable statements.
Sources: test/evidence/in1.test:22-26 test/evidence/in2.test:78-84
Pattern 3: Platform-Specific Alternatives
Some tests provide alternative implementations for different platforms:
skipif mysql
statement ok
CREATE TABLE t7(a TEXT UNIQUE)
onlyif mysql
statement ok
CREATE TABLE t7(a TEXT, UNIQUE (a(1)))
MySQL requires a key length specification for TEXT columns in unique indexes, while other databases do not.
Sources: test/evidence/in1.test:398-405 test/evidence/in1.test:421-427
Platform-Specific Behaviors
SQLite-Specific Extensions
SQLite implements several non-standard SQL features that are explicitly excluded from other platforms:
Empty Right-Hand Side in IN Operator
SQLite allows empty parentheses in IN/NOT IN expressions, which is non-standard:
These tests are marked onlyif sqlite # empty RHS and skipped on MySQL, MSSQL, and Oracle.
Sources: test/evidence/in1.test:16-20 test/evidence/in1.test:22-64 test/evidence/in2.test:7-11 test/evidence/in2.test:78-144
Table-as-Operand Syntax
SQLite allows using a table name directly as the right operand of IN:
This SQLite-specific shorthand is marked onlyif sqlite throughout the test corpus.
Sources: test/evidence/in1.test:69-74 test/evidence/in1.test:80-90
REINDEX Command
SQLite and PostgreSQL support the REINDEX command for rebuilding indexes, while MySQL uses REPAIR TABLE and MSSQL/Oracle have no direct equivalent:
# skip this entire file if ms sql server
onlyif mssql
halt
# skip this entire file if oracle
onlyif oracle
halt
# skip this entire file if mysql
onlyif mysql
halt
Sources: test/evidence/slt_lang_reindex.test:18-32
REPLACE Statement
SQLite's REPLACE INTO syntax (alias for INSERT OR REPLACE) is not supported by MSSQL and Oracle:
# skip this file if ms sql server
onlyif mssql
halt
# skip this file if oracle
onlyif oracle
halt
Sources: test/evidence/slt_lang_replace.test:1-7
MySQL-Specific Limitations and Workarounds
NULL Handling in Subqueries
MySQL has known issues with NULL handling in certain IN subquery contexts:
# mysql is failing this one
skipif mysql
query I nosort label-55
SELECT null IN (SELECT * FROM t4)
----
NULL
These incompatibilities are documented inline and the tests are skipped for MySQL.
Sources: test/evidence/in1.test:951-956 test/evidence/in1.test:970-975
UNIQUE Index Key Length Requirement
MySQL requires explicit key length specifications for TEXT columns in UNIQUE indexes:
skipif mysql
statement ok
CREATE TABLE t7(a TEXT UNIQUE)
onlyif mysql
statement ok
CREATE TABLE t7(a TEXT, UNIQUE (a(1)))
Sources: test/evidence/in1.test:398-405
INSERT OR REPLACE Syntax
MySQL does not support SQLite's INSERT OR REPLACE syntax:
skipif mysql
statement ok
INSERT OR REPLACE INTO t1 VALUES(2, 'insert or replace')
Sources: test/evidence/slt_lang_replace.test:37-46
MSSQL-Specific Differences
DROP INDEX Syntax
MSSQL uses a different syntax for DROP INDEX that requires the table name:
skipif mssql
statement ok
DROP INDEX t1i1;
The entire DROP INDEX test file skips MSSQL-specific statements.
Sources: test/evidence/slt_lang_dropindex.test:22-34
Empty RHS in IN Operator
MSSQL does not support empty parentheses in IN expressions:
skipif mysql # empty RHS
skipif mssql # empty RHS
skipif oracle # empty RHS
query I nosort
SELECT 1 FROM t1 WHERE 1 IN ()
Sources: test/evidence/in2.test:78-84
Oracle-Specific Considerations
VARCHAR vs TEXT Data Type
Oracle uses VARCHAR instead of TEXT for character data:
skipif oracle
statement ok
CREATE TABLE t1( x INTEGER, y TEXT )
onlyif oracle
statement ok
CREATE TABLE t1( x INTEGER, y VARCHAR(8) )
Sources: test/evidence/in2.test:23-29
REINDEX and REPLACE Not Supported
Oracle lacks support for both REINDEX and REPLACE commands, resulting in complete file exclusion for these test suites.
Sources: test/evidence/slt_lang_reindex.test:26-28 test/evidence/slt_lang_replace.test:5-7
graph TB
subgraph Tests["Test Categories"]
CoreSQL["Core SQL\n(SELECT, INSERT, UPDATE)"]
DDL["DDL Commands\n(CREATE, DROP)"]
INNULL["IN/NOT IN with NULL"]
EmptyRHS["Empty RHS in IN"]
TableAsOp["Table-as-operand"]
REINDEX_T["REINDEX Command"]
REPLACE_T["REPLACE Statement"]
end
subgraph Platforms["Database Platforms"]
SQLite["SQLite"]
MySQL["MySQL"]
PostgreSQL["PostgreSQL"]
MSSQL["MSSQL"]
Oracle["Oracle"]
end
CoreSQL -->|Full support| SQLite
CoreSQL -->|Full support| MySQL
CoreSQL -->|Full support| PostgreSQL
CoreSQL -->|Full support| MSSQL
CoreSQL -->|Full support| Oracle
DDL -->|Full support| SQLite
DDL -->|Full support| MySQL
DDL -->|Full support| PostgreSQL
DDL -->|Syntax differences| MSSQL
DDL -->|Type differences| Oracle
INNULL -->|Full support| SQLite
INNULL -->|Partial subquery issues| MySQL
INNULL -->|Full support| PostgreSQL
INNULL -->|Full support| MSSQL
INNULL -->|Full support| Oracle
EmptyRHS -->|Supported non-standard| SQLite
EmptyRHS -->|Not supported| MySQL
EmptyRHS -->|Not supported| PostgreSQL
EmptyRHS -->|Not supported| MSSQL
EmptyRHS -->|Not supported| Oracle
TableAsOp -->|Supported SQLite-specific| SQLite
TableAsOp -->|Not supported| MySQL
TableAsOp -->|Not supported| PostgreSQL
TableAsOp -->|Not supported| MSSQL
TableAsOp -->|Not supported| Oracle
REINDEX_T -->|Full support| SQLite
REINDEX_T -->|REPAIR TABLE instead| MySQL
REINDEX_T -->|Full support| PostgreSQL
REINDEX_T -->|Not supported| MSSQL
REINDEX_T -->|Not supported| Oracle
REPLACE_T -->|Full support| SQLite
REPLACE_T -->|Limited support| MySQL
REPLACE_T -->|Not supported| PostgreSQL
REPLACE_T -->|Not supported| MSSQL
REPLACE_T -->|Not supported| Oracle
style SQLite fill:#f0f0f0
style MySQL fill:#f0f0f0
style PostgreSQL fill:#f0f0f0
style MSSQL fill:#f0f0f0
style Oracle fill:#f0f0f0
Multi-Platform Testing Strategy Matrix
The following diagram illustrates how different test categories map to platform support:
Diagram: Platform Support Matrix for Test Categories
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_reindex.test:18-32 test/evidence/slt_lang_replace.test:1-76
Directive Usage Patterns in Test Files
Common Directive Combinations
The test corpus uses specific directive patterns to handle different compatibility scenarios:
| Pattern | Directive Sequence | Purpose | Example Location |
|---|---|---|---|
| Full file exclusion | onlyif <platform> + halt | Skip entire file for incompatible platforms | test/evidence/slt_lang_reindex.test:22-32 |
| Single statement skip | skipif <platform> + statement | Skip specific statement on incompatible platform | test/evidence/in1.test:398-405 |
| Platform-specific execution | onlyif <platform> + statement | Only execute on specific platform | test/evidence/in1.test:22-26 |
| Alternative implementations | skipif + onlyif pairs | Provide platform-specific alternatives | test/evidence/in1.test:398-427 |
| Labeled test groups | label-N + platform directives | Group related tests across platforms | test/evidence/in1.test:70-78 |
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_reindex.test:1-59 test/evidence/slt_lang_replace.test:1-76
Inline Documentation Comments
Test files include inline comments explaining platform-specific behaviors:
# EVIDENCE-OF: R-64309-54027 Note that SQLite allows the parenthesized
# list of scalar values on the right-hand side of an IN or NOT IN
# operator to be an empty list but most other SQL database engines and
# the SQL92 standard require the list to contain at least one element.
onlyif sqlite # empty RHS
query I nosort
SELECT 1 IN ()
----
0
The # empty RHS suffix provides context for why the directive is needed.
Sources: test/evidence/in1.test:16-26 test/evidence/in2.test:7-11
graph LR
subgraph TestFiles["Test Files"]
in1["in1.test\n(IN operator edge cases)"]
in2["in2.test\n(IN operator validation)"]
dropindex["slt_lang_dropindex.test\n(DROP INDEX)"]
reindex["slt_lang_reindex.test\n(REINDEX)"]
replace["slt_lang_replace.test\n(REPLACE)"]
end
subgraph SQLiteExec["SQLite: Full Execution"]
S1["All tests run"]
S2["Including non-standard features"]
end
subgraph MySQLExec["MySQL: Partial Execution"]
M1["Skip empty RHS tests"]
M2["Skip table-as-operand tests"]
M3["Skip REINDEX tests"]
M4["Skip INSERT OR REPLACE"]
M5["NULL subquery issues"]
end
subgraph MSSQLExec["MSSQL: Partial Execution"]
MS1["File halted: in1.test"]
MS2["File halted: reindex.test"]
MS3["File halted: replace.test"]
MS4["Modified DROP INDEX syntax"]
end
subgraph OracleExec["Oracle: Partial Execution"]
O1["File halted: in1.test"]
O2["File halted: reindex.test"]
O3["File halted: replace.test"]
O4["VARCHAR vs TEXT substitution"]
end
in1 -->|Full| SQLiteExec
in2 -->|Full| SQLiteExec
dropindex -->|Full| SQLiteExec
reindex -->|Full| SQLiteExec
replace -->|Full| SQLiteExec
in1 -->|Partial| MySQLExec
in2 -->|Partial| MySQLExec
reindex -.->|Halted| MySQLExec
replace -->|Partial| MySQLExec
in1 -.->|Halted| MSSQLExec
dropindex -->|Skipped statements| MSSQLExec
reindex -.->|Halted| MSSQLExec
replace -.->|Halted| MSSQLExec
in1 -.->|Halted| OracleExec
in2 -->|With type changes| OracleExec
reindex -.->|Halted| OracleExec
replace -.->|Halted| OracleExec
Test Execution Compatibility Map
The following diagram maps specific test files to their platform compatibility:
Diagram: Test File Execution by Platform
Sources: test/evidence/in1.test:3-9 test/evidence/slt_lang_reindex.test:22-32 test/evidence/slt_lang_replace.test:1-7 test/evidence/slt_lang_dropindex.test:22-28
Best Practices for Cross-Platform Test Design
When adding or modifying tests in the corpus, follow these patterns:
-
Assume SQLite as the Primary Target : All tests should run cleanly on SQLite without directives unless testing non-standard features.
-
Document Non-Standard Features : Use inline comments to explain why platform-specific directives are necessary, citing relevant SQL standard documentation when applicable.
-
Prefer
skipifOveronlyif: When excluding a single platform, useskipif <platform>rather than listing all other platforms withonlyif. This makes tests more maintainable as new platforms are added. -
Use
haltfor Wholesale Incompatibility: If most of a test file relies on platform-specific features, use the halt pattern at the file beginning rather than marking individual statements. -
Provide Alternative Implementations When Possible : For syntax differences (like MySQL's UNIQUE index requirements), provide platform-specific alternatives rather than skipping functionality entirely.
-
Label Related Test Groups : Use the
label-Nsyntax to group related tests that have different implementations across platforms but validate the same behavior.
Sources: test/evidence/in1.test:1-1156 test/evidence/in2.test:1-314
Summary
The sqllogictest corpus achieves cross-database compatibility through a directive-based conditional execution model. SQLite serves as the primary test target with full feature coverage, while MySQL, PostgreSQL, MSSQL, and Oracle have varying levels of support based on their SQL implementation conformance. The skipif, onlyif, and halt directives enable a single test corpus to validate behavior across platforms while gracefully handling platform-specific limitations and non-standard extensions.
For details on how these directives are parsed and executed, see Conditional Execution Directives. For a comprehensive catalog of platform-specific behaviors, see Platform-Specific Behaviors.
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Conditional Execution Directives
Relevant source files
Purpose and Scope
This document specifies the conditional execution directives used in SQL Logic Test files to control which statements and queries execute based on the target database platform. These directives enable a single test file to support multiple SQL implementations while accommodating platform-specific differences in SQL syntax, feature support, and semantic behavior.
For information about the specific platform differences that necessitate these directives, see Platform-Specific Behaviors. For details on the complete test file format specification, see Test File Format Specification.
Overview
Conditional execution directives are line-based control statements that determine whether subsequent test operations should execute, be skipped, or halt file processing entirely. These directives enable tests to adapt their behavior based on which database engine is executing them, allowing the corpus to validate both SQLite-specific extensions and standard SQL behavior across different implementations.
The directives operate on a simple boolean matching model: each directive specifies a target database platform and controls the execution of the immediately following statement or query. When multiple directives appear consecutively, they create conditional execution chains that can handle complex platform-specific scenarios.
Sources: test/evidence/in1.test:1-10 test/evidence/slt_lang_reindex.test:18-32
Directive Types
skipif Directive
The skipif directive prevents execution of the next statement or query when the current database platform matches the specified identifier.
Syntax:
skipif <database-identifier>
Behavior: If the test runner is executing against the specified database, the immediately following test directive (statement or query) is skipped and execution continues with the subsequent line. If the database does not match, the directive has no effect and normal execution proceeds.
Example Usage:
skipif mssql
statement ok
DROP INDEX t1i1;
In this example from test/evidence/slt_lang_dropindex.test:22-24 the DROP INDEX statement is skipped when running on Microsoft SQL Server because MSSQL uses different syntax for dropping indexes (DROP INDEX table.index instead of DROP INDEX index).
Common Patterns:
| Pattern | Purpose | Example Location |
|---|---|---|
skipif mysql | Skip tests for MySQL syntax incompatibilities | test/evidence/in1.test:398-399 |
skipif mssql | Skip tests for MSSQL syntax differences | test/evidence/slt_lang_dropindex.test:22-24 |
Sources: test/evidence/slt_lang_dropindex.test:22-34 test/evidence/in1.test:398-404 test/evidence/in1.test:421-427
onlyif Directive
The onlyif directive ensures that the next statement or query executes only when the current database platform matches the specified identifier.
Syntax:
onlyif <database-identifier>
Behavior: If the test runner is executing against the specified database, the immediately following test directive proceeds normally. If the database does not match, the following directive is skipped and execution continues.
Example Usage:
onlyif sqlite # empty RHS
query I nosort
SELECT 1 IN ()
----
0
This example from test/evidence/in1.test:22-26 demonstrates a SQLite-specific feature where the IN operator can accept an empty set on the right-hand side. The comment # empty RHS provides context that this test validates non-standard behavior.
File-Level Halting Pattern:
onlyif mssql
halt
When combined with halt, the onlyif directive creates an early-exit mechanism for entire files. The pattern at test/evidence/slt_lang_reindex.test:23-24 causes the test runner to stop processing the file immediately when running on MSSQL, effectively skipping all tests in that file.
Common Patterns:
| Pattern | Purpose | Example Location |
|---|---|---|
onlyif sqlite | Execute SQLite-specific extensions | test/evidence/in1.test:22-26 |
onlyif mysql | Execute MySQL-specific syntax | test/evidence/in1.test:402-404 |
onlyif mssql + halt | Skip entire file on MSSQL | test/evidence/slt_lang_reindex.test:23-24 |
onlyif oracle + halt | Skip entire file on Oracle | test/evidence/in1.test:8-9 |
Sources: test/evidence/in1.test:4-9 test/evidence/in1.test:22-26 test/evidence/slt_lang_reindex.test:23-32
halt Directive
The halt directive immediately stops processing the current test file when encountered.
Syntax:
halt
Behavior: When executed, this directive causes the test runner to cease reading and processing any remaining lines in the file. Execution then proceeds to the next test file in the suite.
Typical Usage Pattern:
The halt directive is almost always used in combination with onlyif to create platform-specific file exclusions:
# skip this entire file if ms sql server
onlyif mssql
halt
# skip this entire file if oracle
onlyif oracle
halt
This pattern from test/evidence/slt_lang_reindex.test:22-28 demonstrates the idiomatic approach: conditional directives at the beginning of a file determine whether the entire file should be skipped based on database platform compatibility.
Sources: test/evidence/in1.test:4-9 test/evidence/slt_lang_reindex.test:22-32
Database Platform Identifiers
Conditional directives recognize the following database platform identifiers:
| Identifier | Database System | Common Usage |
|---|---|---|
sqlite | SQLite | Testing SQLite-specific extensions and non-standard features |
mysql | MySQL | Testing MySQL syntax variations or skipping incompatible features |
mssql | Microsoft SQL Server | Skipping MSSQL-incompatible syntax or testing MSSQL-specific behavior |
oracle | Oracle Database | Skipping Oracle-incompatible features |
postgresql | PostgreSQL | Testing standard SQL compliance on PostgreSQL |
The identifier comparison is case-sensitive and must match exactly as shown above.
Sources: test/evidence/in1.test:4-9 test/evidence/slt_lang_reindex.test:23-32 test/evidence/in1.test:398-427
Directive Execution Model
Single-Statement Control Flow
Sources: test/evidence/in1.test:22-65 test/evidence/slt_lang_dropindex.test:22-34
File-Level Halt Pattern
The most common pattern for excluding entire files based on database platform uses consecutive onlyif + halt pairs:
Sources: test/evidence/in1.test:1-10 test/evidence/slt_lang_reindex.test:22-32
Label-Based Test Organization
Conditional directives work in conjunction with the label system to enable platform-specific variations of the same logical test:
onlyif sqlite
query I nosort label-1
SELECT 1 IN t1
----
0
query I nosort label-1
SELECT 1 IN (SELECT * FROM t1)
----
0
In this pattern from test/evidence/in1.test:69-78 both queries share the same label-1 identifier. The first query uses SQLite's non-standard table-as-operand syntax (1 IN t1), which only executes on SQLite. The second query uses standard SQL subquery syntax and executes on all platforms. Both validate the same logical behavior but accommodate different SQL syntax support.
Label Pattern Analysis:
| Label Pattern | SQLite-Specific Test | Standard SQL Test | Purpose |
|---|---|---|---|
label-1 | SELECT 1 IN t1 | SELECT 1 IN (SELECT * FROM t1) | Empty set IN operator |
label-13 | SELECT 1.23 IN t1 | SELECT 1.23 IN (SELECT * FROM t1) | Float value IN empty set |
label-19 | SELECT 1 IN t4 | SELECT 1 IN (SELECT * FROM t4) | Integer IN non-empty set |
This pattern appears extensively throughout test/evidence/in1.test:69-549 with each label representing a distinct test case that has both SQLite-specific and portable implementations.
Sources: test/evidence/in1.test:69-158 test/evidence/in1.test:239-260
Directive Combination Patterns
Multiple Platform Exclusions
Files that are incompatible with multiple platforms use sequential onlyif + halt pairs:
# skip this entire file if ms sql server
onlyif mssql
halt
# skip this entire file if oracle
onlyif oracle
halt
# skip this entire file if mysql
onlyif mysql
halt
This pattern from test/evidence/slt_lang_reindex.test:22-32 ensures the REINDEX command tests only run on SQLite and PostgreSQL, as these are the only major SQL implementations that support the REINDEX statement.
Sources: test/evidence/slt_lang_reindex.test:18-32
Inline Conditional Execution
For individual statement variations, directives appear immediately before the affected test:
skipif mysql
statement ok
CREATE TABLE t7(a TEXT UNIQUE)
onlyif mysql
statement ok
CREATE TABLE t7(a TEXT, UNIQUE (a(1)))
This pattern from test/evidence/in1.test:398-404 handles MySQL's requirement for indexed prefix lengths on TEXT columns. The first statement creates a standard unique constraint for all databases except MySQL. The second statement uses MySQL's indexed prefix syntax UNIQUE (a(1)) only when running on MySQL.
Sources: test/evidence/in1.test:398-427
Platform-Specific Comment Annotations
Directives often include inline comments explaining why the conditional execution is necessary:
onlyif sqlite # empty RHS
query I nosort
SELECT 1 IN ()
----
0
The comment # empty RHS from test/evidence/in1.test:22-26 documents that this test validates SQLite's non-standard support for empty right-hand side sets in IN expressions. These annotations serve as inline documentation for developers reading the test files.
Sources: test/evidence/in1.test:22-26 test/evidence/in1.test:38-42
Directive Scope and Limitations
Single-Statement Scope
Each skipif or onlyif directive affects exactly one subsequent statement or query directive. Multiple directives cannot be batched under a single conditional:
Valid Pattern:
onlyif sqlite
query I nosort
SELECT 1 IN t1
----
0
onlyif sqlite
query I nosort
SELECT 1 NOT IN t1
----
1
Invalid Pattern (Not Supported):
onlyif sqlite
query I nosort
SELECT 1 IN t1
----
0
query I nosort
SELECT 1 NOT IN t1 # This will execute unconditionally
----
1
Each conditional test requires its own directive prefix, as seen in test/evidence/in1.test:69-90 where consecutive onlyif sqlite directives each control a single query.
Sources: test/evidence/in1.test:69-90 test/evidence/in1.test:163-205
No Nested Conditionals
The directive system does not support logical combinations or nested conditions. Each directive evaluates independently:
- No AND logic:
skipif mysql AND oracle(not supported) - No OR logic:
onlyif sqlite OR postgresql(not supported) - No negation:
skipif NOT mysql(not supported)
To exclude a test from multiple platforms, use separate directive-statement pairs or the file-level halt pattern.
Sources: test/evidence/slt_lang_reindex.test:22-32
Integration with Test Execution
Test Runner Requirements
To implement conditional execution support, test runners must:
- Identify the target platform: Maintain a database identifier string matching one of the recognized platform identifiers
- Parse directives: Recognize
skipif,onlyif, andhaltkeywords at the beginning of lines - Evaluate conditions: Compare the directive's database identifier against the runner's platform identifier
- Control execution flow: Skip or execute the next directive based on the evaluation result
- Handle halt: Immediately terminate file processing when a
haltdirective executes
Sources: test/evidence/in1.test:1-10 test/evidence/slt_lang_dropindex.test:22-34
Execution State Machine
Sources: test/evidence/in1.test:1-65 test/evidence/slt_lang_reindex.test:22-38
Usage Statistics
Analysis of the provided test files reveals the frequency and distribution of conditional directives:
in1.test Directive Distribution
| Directive Pattern | Occurrences | Purpose |
|---|---|---|
onlyif sqlite | ~150 instances | SQLite-specific table-as-operand syntax and empty RHS support |
skipif mysql | 6 instances | MySQL TEXT column unique constraint limitations |
onlyif mysql | 6 instances | MySQL-specific indexed prefix syntax |
onlyif mssql + halt | 1 instance | Exclude entire file from MSSQL |
onlyif oracle + halt | 1 instance | Exclude entire file from Oracle |
Analysis: The heavy use of onlyif sqlite in test/evidence/in1.test reflects this file's focus on validating SQLite's non-standard IN operator extensions, particularly the table-as-operand syntax and empty set support.
Sources: test/evidence/in1.test:1-1156
slt_lang_reindex.test Directive Distribution
| Directive Pattern | Occurrences | Purpose |
|---|---|---|
onlyif mssql + halt | 1 instance | REINDEX not supported on MSSQL |
onlyif oracle + halt | 1 instance | REINDEX not supported on Oracle |
onlyif mysql + halt | 1 instance | MySQL uses REPAIR TABLE instead |
Analysis: This file uses exclusively file-level halt directives, as the REINDEX command is fundamentally incompatible with most database systems except SQLite and PostgreSQL.
Sources: test/evidence/slt_lang_reindex.test:18-32
slt_lang_dropindex.test Directive Distribution
| Directive Pattern | Occurrences | Purpose |
|---|---|---|
skipif mssql | 3 instances | MSSQL uses DROP INDEX table.index syntax |
Analysis: The consistent use of skipif mssql before DROP INDEX statements reflects MSSQL's requirement to qualify the index name with its table name.
Sources: test/evidence/slt_lang_dropindex.test:22-34
This conditional execution system enables the SQL Logic Test corpus to serve as a comprehensive validation suite across multiple database platforms while maintaining a single, unified test file format. The simplicity of the directive syntax—limited to three keywords and platform-specific matching—ensures that test files remain readable and maintainable while supporting complex cross-platform testing scenarios.
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Platform-Specific Behaviors
Relevant source files
- test/evidence/in1.test
- test/evidence/in2.test
- test/evidence/slt_lang_createview.test
- test/evidence/slt_lang_reindex.test
Purpose and Scope
This page catalogs the known SQL implementation differences between SQLite, MySQL, MSSQL, Oracle, and PostgreSQL as revealed by the test corpus. These differences determine when tests must use conditional execution directives (skipif, onlyif, halt) to adapt to platform-specific SQL semantics.
For information about the conditional execution directives themselves, see Conditional Execution Directives. For practical examples of integrating these tests into CI/CD pipelines, see Integrating with CI/CD Systems.
Platform Compatibility Overview
The test corpus supports five major database platforms, each with varying levels of SQL standard compliance and proprietary extensions. The following diagram illustrates the primary compatibility categories:
Diagram: Platform Support Matrix
graph TB
subgraph "SQLite Features"
SQLITE_EMPTY["Empty RHS in IN: IN ()"]
SQLITE_TABLE["Table-as-operand: SELECT 1 IN t1"]
SQLITE_REINDEX["REINDEX command"]
SQLITE_TEMP["TEMP VIEW support"]
SQLITE_READONLY["Strict read-only views"]
end
subgraph "MySQL Compatibility"
MYSQL_NO_EMPTY["Rejects empty IN ()"]
MYSQL_UNIQUE_LEN["UNIQUE requires length on TEXT"]
MYSQL_NO_TABLE["Requires subquery syntax"]
MYSQL_REPAIR["Uses REPAIR TABLE instead"]
end
subgraph "MSSQL Compatibility"
MSSQL_NO_EMPTY["Rejects empty IN ()"]
MSSQL_VIEW_DML["Allows DELETE/UPDATE on views"]
MSSQL_NO_REINDEX["No REINDEX support"]
end
subgraph "Oracle Compatibility"
ORACLE_NO_EMPTY["Rejects empty IN ()"]
ORACLE_VARCHAR["VARCHAR vs TEXT differences"]
ORACLE_NO_REINDEX["No REINDEX support"]
end
subgraph "PostgreSQL Compatibility"
POSTGRES_REINDEX["REINDEX supported"]
POSTGRES_STANDARD["High SQL standard compliance"]
end
Sources: test/evidence/in1.test:1-20 test/evidence/slt_lang_reindex.test:18-32 test/evidence/slt_lang_createview.test:65-86
Empty RHS in IN Operator
SQLite Extension : SQLite allows the right-hand side of IN and NOT IN operators to be an empty list, returning deterministic results regardless of the left operand.
| Left Operand | Expression | SQLite Result | Standard Behavior |
|---|---|---|---|
| Any value | x IN () | false (0) | Syntax error |
| Any value | x NOT IN () | true (1) | Syntax error |
| NULL | NULL IN () | false (0) | Syntax error |
| NULL | NULL NOT IN () | true (1) | Syntax error |
Platform Behavior :
Test File Implementation :
The test corpus uses onlyif sqlite directives to restrict empty RHS tests to SQLite:
onlyif sqlite # empty RHS
query I nosort
SELECT 1 IN ()
----
0
Sources: test/evidence/in1.test:11-26 test/evidence/in1.test:38-64 test/evidence/in2.test:2-11 test/evidence/in2.test:76-144
Table-as-Operand in IN Clause
SQLite Extension : SQLite allows a table name to appear directly as the right operand in IN expressions, treating it as shorthand for a subquery selecting all rows.
Syntax Comparison :
| Platform | Direct Table Syntax | Subquery Syntax | Behavior |
|---|---|---|---|
| SQLite | SELECT 1 IN t1 | SELECT 1 IN (SELECT * FROM t1) | Both valid |
| MySQL | Syntax error | SELECT 1 IN (SELECT * FROM t1) | Subquery required |
| MSSQL | Syntax error | SELECT 1 IN (SELECT * FROM t1) | Subquery required |
| Oracle | Syntax error | SELECT 1 IN (SELECT * FROM t1) | Subquery required |
| PostgreSQL | Syntax error | SELECT 1 IN (SELECT * FROM t1) | Subquery required |
Diagram: IN Clause Syntax Patterns
Test Pattern :
The corpus uses dual-labeled tests where SQLite tests the direct syntax with onlyif sqlite, and all platforms test the subquery syntax:
onlyif sqlite
query I nosort label-1
SELECT 1 IN t1
----
0
query I nosort label-1
SELECT 1 IN (SELECT * FROM t1)
----
0
Sources: test/evidence/in1.test:69-158 test/evidence/in1.test:239-327 test/evidence/in1.test:441-549
REINDEX Command Support
Platform Differences : The REINDEX command for rebuilding indexes exists only in SQLite and PostgreSQL, with no equivalent in the SQL standard.
Diagram: REINDEX Command Routing
flowchart TD
CMD["REINDEX t1i1"]
CMD --> CHECK_PLATFORM{"Database\nPlatform?"}
CHECK_PLATFORM -->|SQLite| SQLITE_EXEC["Execute REINDEX\nRebuild index"]
CHECK_PLATFORM -->|PostgreSQL| POSTGRES_EXEC["Execute REINDEX\nRebuild index"]
CHECK_PLATFORM -->|MySQL| MYSQL_ALT["Use REPAIR TABLE instead\nDifferent semantics"]
CHECK_PLATFORM -->|MSSQL| MSSQL_HALT["halt directive\nTest not applicable"]
CHECK_PLATFORM -->|Oracle| ORACLE_HALT["halt directive\nTest not applicable"]
SQLITE_EXEC --> SUCCESS["Index rebuilt"]
POSTGRES_EXEC --> SUCCESS
MYSQL_ALT --> MYSQL_SUCCESS["Table repaired"]
MSSQL_HALT --> SKIP["Test skipped"]
ORACLE_HALT --> SKIP
Implementation Table :
| Platform | REINDEX Support | Alternative | Test Directive |
|---|---|---|---|
| SQLite | ✓ Full support | N/A | No directive needed |
| PostgreSQL | ✓ Full support | N/A | No directive needed |
| MySQL | ✗ Not supported | REPAIR TABLE [tbl_name] | onlyif mysql + halt |
| MSSQL | ✗ Not supported | Automatic maintenance | onlyif mssql + halt |
| Oracle | ✗ Not supported | Automatic maintenance | onlyif oracle + halt |
Test File Halt Pattern :
# skip this entire file if ms sql server
onlyif mssql
halt
# skip this entire file if oracle
onlyif oracle
halt
# skip this entire file if mysql
onlyif mysql
halt
Sources: test/evidence/slt_lang_reindex.test:18-32 test/evidence/slt_lang_reindex.test:34-38
View Mutability
Critical Difference : MSSQL allows DML operations (DELETE, UPDATE) on views, while SQLite enforces strict read-only semantics. This represents a significant behavioral divergence.
Platform Comparison Table :
| Operation | SQLite | MySQL | MSSQL | Oracle | PostgreSQL | SQL Standard |
|---|---|---|---|---|---|---|
DELETE FROM view | Error | Error | Allowed | Error | Conditional | Disallowed |
INSERT INTO view | Error | Error | Error | Error | Conditional | Disallowed |
UPDATE view SET | Error | Error | Allowed | Error | Conditional | Disallowed |
| INSTEAD OF triggers | ✓ Supported | Limited | ✓ Supported | ✓ Supported | ✓ Supported | Optional |
Diagram: View DML Operation Flow
Test Implementation Pattern :
# MSSQL allows DELETE on views
skipif mssql # this is allowed
statement error
DELETE FROM view1 WHERE x>0
onlyif mssql # this is allowed
statement ok
DELETE FROM view1 WHERE x>0
# MSSQL allows UPDATE on views
skipif mssql # this is allowed
statement error
UPDATE view1 SET x=2
onlyif mssql # this is allowed
statement ok
UPDATE view1 SET x=2
Sources: test/evidence/slt_lang_createview.test:65-86 test/evidence/slt_lang_createview.test:87-103
Temporary View Support
SQLite Feature : SQLite supports the TEMP and TEMPORARY keywords for creating session-scoped views that are automatically dropped when the database connection closes.
Syntax Availability :
| Platform | CREATE TEMP VIEW | CREATE TEMPORARY VIEW | Notes |
|---|---|---|---|
| SQLite | ✓ Supported | ✓ Supported | Auto-deleted on close |
| MySQL | Limited | Limited | Different syntax/semantics |
| MSSQL | Different | Different | Uses temp tables instead |
| Oracle | Different | Different | Uses global temp tables |
| PostgreSQL | ✓ Supported | ✓ Supported | Similar to SQLite |
Test Pattern :
onlyif sqlite
statement ok
CREATE TEMP VIEW view2 AS SELECT x FROM t1 WHERE x>0
onlyif sqlite
statement ok
CREATE TEMPORARY VIEW view3 AS SELECT x FROM t1 WHERE x>0
Sources: test/evidence/slt_lang_createview.test:42-53
NULL Handling in IN Operator
MySQL-Specific Issue : MySQL exhibits inconsistent behavior when evaluating NULL IN (SELECT ...) compared to other platforms.
Behavior Matrix :
| Expression | SQLite | MySQL | Expected Result |
|---|---|---|---|
NULL IN (SELECT * FROM t4) | Returns NULL | Fails/Different | NULL |
NULL NOT IN (SELECT * FROM t4) | Returns NULL | Fails/Different | NULL |
NULL IN (2,3,4) | Returns NULL | Returns NULL | NULL |
Test Compensation :
# mysql is failing this one
skipif mysql
query I nosort label-55
SELECT null IN (SELECT * FROM t4)
----
NULL
# mysql is failing this one
skipif mysql
query I nosort label-56
SELECT null NOT IN (SELECT * FROM t4)
----
NULL
Sources: test/evidence/in1.test:951-975 test/evidence/in1.test:1005-1023
UNIQUE Constraint on TEXT Columns
MySQL Limitation : MySQL requires an explicit length specification when creating UNIQUE constraints on TEXT columns, while other platforms infer appropriate defaults.
Platform-Specific CREATE TABLE Patterns :
Test Implementation :
skipif mysql
statement ok
CREATE TABLE t7(a TEXT UNIQUE)
onlyif mysql
statement ok
CREATE TABLE t7(a TEXT, UNIQUE (a(1)))
The MySQL-specific syntax UNIQUE (a(1)) creates a unique index on the first character of the TEXT column.
Sources: test/evidence/in1.test:398-405 test/evidence/in1.test:421-428
Data Type Compatibility
Oracle-Specific : Oracle uses VARCHAR for variable-length character data, while SQLite and other platforms commonly use TEXT.
Type Mapping Table :
| Logical Type | SQLite | MySQL | MSSQL | Oracle | PostgreSQL |
|---|---|---|---|---|---|
| Variable text | TEXT | TEXT | TEXT/VARCHAR | VARCHAR | TEXT |
| Fixed text | TEXT | CHAR | CHAR | CHAR | CHAR |
| Large text | TEXT | TEXT/LONGTEXT | TEXT/VARCHAR(MAX) | CLOB | TEXT |
Conditional Schema Creation :
skipif oracle
statement ok
CREATE TABLE t1( x INTEGER, y TEXT )
onlyif oracle
statement ok
CREATE TABLE t1( x INTEGER, y VARCHAR(8) )
Sources: test/evidence/in2.test:23-29
stateDiagram-v2
[*] --> FileStart : Open test file
FileStart --> CheckDirective : Read directive
CheckDirective --> OnlyIfMSSQL : onlyif mssql
CheckDirective --> OnlyIfOracle : onlyif oracle
CheckDirective --> OnlyIfMySQL : onlyif mysql
CheckDirective --> ProceedTests : No platform halt
OnlyIfMSSQL --> IsMSSQL{Running\nMSSQL?}
OnlyIfOracle --> IsOracle{Running\nOracle?}
OnlyIfMySQL --> IsMySQL{Running\nMySQL?}
IsMSSQL -->|Yes| Halt : halt
IsMSSQL -->|No| ProceedTests
IsOracle -->|Yes| Halt
IsOracle -->|No| ProceedTests
IsMySQL -->|Yes| Halt
IsMySQL -->|No| ProceedTests
Halt --> [*] : Skip entire file
ProceedTests --> RunTests : Execute tests
Platform Halt Strategy
File-Level Exclusion : Some test files are entirely incompatible with certain platforms and use the halt directive at the file start to skip execution.
Diagram: File-Level Halt Decision Tree
Examples from Test Files :
in1.test excludes MSSQL and Oracle:
# skip this entire file if ms sql server
onlyif mssql
halt
# skip this entire file if oracle
onlyif oracle
halt
slt_lang_reindex.test excludes MSSQL, Oracle, and MySQL:
# skip this entire file if ms sql server
onlyif mssql
halt
# skip this entire file if oracle
onlyif oracle
halt
# skip this entire file if mysql
onlyif mysql
halt
Sources: test/evidence/in1.test:3-9 test/evidence/slt_lang_reindex.test:22-32
Summary: Cross-Platform Testing Matrix
The following table summarizes key SQL feature support across platforms:
| Feature | SQLite | MySQL | MSSQL | Oracle | PostgreSQL |
|---|---|---|---|---|---|
| Empty IN () | ✓ | ✗ | ✗ | ✗ | ✗ |
| Table-as-operand | ✓ | ✗ | ✗ | ✗ | ✗ |
| REINDEX | ✓ | ✗ (REPAIR) | ✗ | ✗ | ✓ |
| TEMP VIEW | ✓ | Limited | Different | Different | ✓ |
| View DML | ✗ (strict) | ✗ | ✓ (allowed) | ✗ | Conditional |
| NULL IN SELECT | ✓ | Issues | ✓ | ✓ | ✓ |
| TEXT UNIQUE | ✓ | Length req'd | ✓ | VARCHAR | ✓ |
These platform differences necessitate extensive use of conditional directives throughout the test corpus to ensure accurate validation across all supported database engines.
Sources: test/evidence/in1.test:1-1156 test/evidence/in2.test:1-314 test/evidence/slt_lang_createview.test:1-129 test/evidence/slt_lang_reindex.test:1-59
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Usage Guide
Relevant source files
Purpose and Scope
This guide provides practical instructions for using the sqlite-sqllogictest-corpus repository to extract and utilize SQLite's official SQL Logic Test corpus. It covers local extraction workflows, CI/CD integration patterns, and common usage scenarios.
For architectural details about the extraction pipeline, see System Architecture. For detailed information about the test file format and directives, see Test File Format Specification. For building and running the extractor in detail, see Building and Running the Extractor. For CI/CD integration examples, see Integrating with CI/CD Systems.
Prerequisites
Before using the corpus extraction system, ensure the following are available:
| Requirement | Purpose | Minimum Version |
|---|---|---|
| Docker Engine | Container runtime for slt-gen image | 20.10+ |
| Git | Repository cloning and version control | 2.30+ |
| Disk Space | Test corpus storage | ~500 MB free |
| Network Access | Fossil repository cloning | HTTPS outbound |
The extraction process requires no additional dependencies as all tools (Fossil SCM, bash, tcl) are bundled within the Docker image.
Sources: Dockerfile:1-36 README.md:1-27
Local Extraction Workflow
Docker Image Build Process
The following diagram illustrates the multi-stage process for building the slt-gen Docker image:
flowchart TD
START["docker build -t slt-gen ."] --> BASE["FROM debian:stable-slim"]
BASE --> PKGS["apt-get install\nfossil, bash, build-essential,\nca-certificates, curl, tcl"]
PKGS --> CLONE["fossil clone\nhttps://www.sqlite.org/sqllogictest/\n/src/sqllogictest.fossil"]
CLONE --> OPEN["fossil open\n/src/sqllogictest.fossil"]
OPEN --> SCRIPT["Create /usr/local/bin/slt-extract\nCopy /src/test to destination"]
SCRIPT --> ENTRY["ENTRYPOINT slt-extract"]
ENTRY --> COMPLETE["Image: slt-gen\nReady for extraction"]
style START fill:#f9f9f9
style COMPLETE fill:#f9f9f9
Sources: Dockerfile:1-36
Building the Image
Execute the build command from the repository root:
The build process performs the following operations:
- Base Layer : Initializes Debian stable-slim environment Dockerfile1
- Dependency Installation : Installs Fossil SCM and build tools Dockerfile:5-12
- Repository Cloning : Clones the official sqllogictest Fossil repository Dockerfile:15-17
- Extractor Script : Creates the
slt-extractutility Dockerfile:20-31 - Entry Point Configuration : Sets container entry point to
slt-extractDockerfile35
Build output confirms successful layer creation and Fossil repository synchronization.
Sources: Dockerfile:1-36 README.md:7-11
Volume Mounting Architecture
The extraction process uses Docker volume mounting to transfer files from the container to the host filesystem:
flowchart LR
subgraph HOST["Host Filesystem"]
CWD["$PWD"]
TEST_DIR["test/"]
end
subgraph CONTAINER["slt-gen Container"]
SRC["/src/test/\nFossil repository tests"]
WORK["/work/test/\nVolume mount point"]
EXTRACT["slt-extract script"]
end
CWD --> TEST_DIR
TEST_DIR -.->|Volume mount -v $PWD/test:/work/test| WORK
SRC -->|cp -R| WORK
EXTRACT -->|Executes copy| SRC
WORK -.->|Persists to| TEST_DIR
style HOST fill:#f9f9f9
style CONTAINER fill:#f9f9f9
Sources: Dockerfile:20-31 .github/workflows/update-corpus.yml:28-31
Running the Extractor
Execute the extraction sequence with the following commands:
Command breakdown:
| Command | Purpose | File Reference |
|---|---|---|
rm -rf test | Remove existing test directory | .github/workflows/update-corpus.yml29 |
mkdir test | Create fresh test directory | .github/workflows/update-corpus.yml30 |
docker run --rm | Run container with auto-removal | .github/workflows/update-corpus.yml31 |
-v "$PWD/test:/work/test" | Mount host directory to container | .github/workflows/update-corpus.yml31 |
slt-gen | Image name (runs slt-extract entrypoint) | .github/workflows/update-corpus.yml31 |
The slt-extract script copies all files from /src/test (Fossil repository content) to /work/test (volume-mounted host directory) Dockerfile:24-28
Expected output:
copied corpus to /work/test
Sources: .github/workflows/update-corpus.yml:28-31 Dockerfile:20-31 README.md:13-19
Working with Extracted Tests
Directory Structure Post-Extraction
After successful extraction, the test/ directory contains the complete corpus organized by test category:
test/
├── evidence/
│ ├── slt_lang_createtrigger.test
│ ├── slt_lang_createview.test
│ ├── slt_lang_dropindex.test
│ ├── slt_lang_droptable.test
│ ├── slt_lang_droptrigger.test
│ ├── slt_lang_dropview.test
│ ├── slt_lang_reindex.test
│ ├── slt_lang_replace.test
│ ├── slt_lang_update.test
│ ├── slt_lang_aggfunc.test
│ ├── in1.test
│ └── in2.test
└── index/
└── between/
├── 1/
├── 10/
└── 100/
For detailed organization structure, see Test Organization Structure.
Sources: Dockerfile:24-28
Verifying Extraction Success
Validate the extraction by checking for expected test files:
The corpus should contain hundreds of .test files organized into evidence/ and index/ subdirectories.
Sources: .github/workflows/update-corpus.yml:36-44
Automated Update Workflow
sequenceDiagram
participant SCHED as "GitHub Scheduler"
participant WF as "update-corpus.yml"
participant DOCKER as "Docker Engine"
participant FOSSIL as "Fossil Repository"
participant GIT as "Git Repository"
Note over SCHED: Every Monday 06:00 UTC
SCHED->>WF: Trigger workflow
WF->>WF: Checkout repository\nactions/checkout@v4
WF->>DOCKER: docker build -t slt-gen .
DOCKER->>FOSSIL: fossil clone\nhttps://www.sqlite.org/sqllogictest/
FOSSIL-->>DOCKER: Repository data
DOCKER-->>WF: Image ready
WF->>WF: rm -rf test && mkdir test
WF->>DOCKER: docker run --rm\n-v $PWD/test:/work/test slt-gen
DOCKER->>WF: Extraction complete
WF->>WF: git status --porcelain
alt "Changes detected & not PR"
WF->>GIT: git add test
WF->>GIT: git commit -m 'Update corpus'
WF->>GIT: git push
else "No changes or is PR"
WF->>WF: Skip commit
end
GitHub Actions Integration
The repository includes a pre-configured GitHub Actions workflow that automates corpus updates:
Sources: .github/workflows/update-corpus.yml:1-45
Workflow Triggers
The workflow activates under three conditions:
| Trigger Type | Schedule/Event | Purpose | Configuration |
|---|---|---|---|
| Scheduled | Every Monday 06:00 UTC | Weekly automatic updates | .github/workflows/update-corpus.yml:4-6 |
| Manual | workflow_dispatch | On-demand corpus refresh | .github/workflows/update-corpus.yml7 |
| Pull Request | Path changes | Testing workflow modifications | .github/workflows/update-corpus.yml:8-12 |
The scheduled trigger uses cron syntax "0 6 * * 1" .github/workflows/update-corpus.yml6
Sources: .github/workflows/update-corpus.yml:3-12
flowchart TD
EXTRACT["Extraction complete"] --> STATUS["git status --porcelain"]
STATUS --> CHECK_EVENT{"Event type?"}
CHECK_EVENT -->|pull_request| SKIP["Skip commit\nPrevent PR pollution"]
CHECK_EVENT -->|schedule or workflow_dispatch| CHECK_CHANGES{"Changes\ndetected?"}
CHECK_CHANGES -->|Output exists| COMMIT["git config user\ngit add test\ngit commit\ngit push"]
CHECK_CHANGES -->|No output| NO_UPDATE["Echo 'No updates'\nSkip commit"]
SKIP --> END["Workflow complete"]
COMMIT --> END
NO_UPDATE --> END
Change Detection and Commit Logic
The workflow implements intelligent change detection to avoid unnecessary commits:
The conditional logic at .github/workflows/update-corpus.yml34 prevents commits during pull request builds, while .github/workflows/update-corpus.yml36 uses git status --porcelain to detect file changes.
Sources: .github/workflows/update-corpus.yml:33-44
Integration Patterns
CI/CD Pipeline Integration
The corpus can be integrated into various continuous integration systems using the extraction workflow.
Pattern 1: Direct Docker Execution
For CI systems with Docker support (GitHub Actions, GitLab CI, Circle CI):
This pattern builds the extractor image directly from the repository URL without cloning.
Sources: .github/workflows/update-corpus.yml:24-31 README.md:7-19
Pattern 2: Repository Cloning
For systems preferring Git-based workflows:
This pattern provides version control for the extraction infrastructure.
Sources: README.md:1-27
Pattern 3: Pre-extracted Corpus Usage
For CI systems with limited Docker support:
This pattern uses the pre-extracted corpus committed to the repository, avoiding Docker dependency.
Sources: .github/workflows/update-corpus.yml39
Local Development Workflow
For iterative local testing during SQL engine development:
The build step is executed once; extraction is refreshed periodically to obtain upstream updates.
Sources: README.md:5-19
Common Usage Scenarios
Scenario 1: Database Vendor Compliance Testing
Database vendors can validate SQL standard compliance:
For conditional execution details, see Conditional Execution Directives.
Sources: README.md:5-19
Scenario 2: SQL Parser Development
Parser developers can use the corpus for syntax validation:
This extracts raw SQL statements without execution context for parser-only validation.
Sources: Dockerfile:24-28
Scenario 3: Research and SQL Semantics Analysis
Researchers can analyze SQL behavior patterns:
For aggregate function test details, see Aggregate Function Tests.
Sources: Dockerfile:24-28
Troubleshooting
Common Issues and Solutions
| Issue | Symptom | Solution | Reference |
|---|---|---|---|
| Build failure | Docker build fails with network error | Check HTTPS access to sqlite.org | Dockerfile15 |
| Empty test directory | test/ directory created but empty | Verify volume mount path is absolute | Dockerfile25 |
| Permission errors | Cannot write to mounted volume | Ensure Docker has write permissions to host directory | .github/workflows/update-corpus.yml31 |
| Stale corpus | Tests outdated compared to upstream | Rebuild image to re-clone Fossil repository | Dockerfile:15-17 |
Verifying Fossil Repository Clone
To confirm successful Fossil repository cloning during image build:
The Fossil repository clone occurs at Dockerfile:15-17 during image build.
Sources: Dockerfile:14-17
Manual Extraction Script Execution
For debugging extraction issues, the slt-extract script can be executed manually:
The script implementation is defined at Dockerfile:20-31
Sources: Dockerfile:20-33
Advanced Usage
Custom Extraction Destination
Override the default extraction path by passing an argument:
The slt-extract script accepts a destination path parameter Dockerfile25
Sources: Dockerfile25
Selective Test Extraction
Extract only specific test categories using volume filtering:
Alternatively, modify the slt-extract script for selective copying before building the image.
Sources: Dockerfile:24-28
Image Size Optimization
The built image contains the full Fossil repository and build tools. For production CI/CD, consider:
- Multi-stage builds : Separate Fossil clone from final extraction
- Volume caching : Cache built image to avoid rebuilds
- Pre-extracted artifacts : Use repository-committed tests directly
The current single-stage design prioritizes simplicity over size optimization Dockerfile:1-36
Sources: Dockerfile:1-36
Next Steps
After extracting the corpus:
- Implement test runner : Parse and execute
.testfiles against your SQL engine. See Test File Format Specification for format details. - Filter by database platform : Use conditional directives to select relevant tests. See Conditional Execution Directives.
- Set up automated testing : Integrate extraction into CI/CD pipeline. See Integrating with CI/CD Systems.
- Explore test categories : Review available test types and coverage. See Test Corpus Reference.
Sources: README.md:1-27
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Building and Running the Extractor
Relevant source files
Purpose and Scope
This page provides step-by-step instructions for building the slt-gen Docker image and running the extraction process to obtain a local copy of the SQLite sqllogictest corpus. It covers the practical mechanics of using the Docker-based extraction system locally, including prerequisites, build commands, and execution steps.
For information about how the automated update workflow uses this extraction system in CI/CD, see Automated Update Workflow. For information about integrating the corpus into your own CI/CD pipelines after extraction, see Integrating with CI/CD Systems.
Prerequisites
The extraction system requires the following tools to be installed on your local machine:
| Tool | Purpose | Minimum Version |
|---|---|---|
| Docker | Container runtime for building and executing the slt-gen image | 20.10+ |
| Bash | Shell for executing extraction commands | 4.0+ |
| Git | (Optional) Version control if cloning this repository | 2.0+ |
The Docker image itself handles all other dependencies internally, including the Fossil SCM client and the SQLite repository cloning process.
Sources: README.md:1-27 Dockerfile:1-36
Building the Docker Image
Build Command
The Docker image is built using the standard docker build command with the tag slt-gen:
This command must be executed from the repository root directory where the Dockerfile is located.
Sources: README.md:9-11
Build Process Overview
Build Process: Docker Image Construction Pipeline
Sources: Dockerfile:1-36
Build Steps Breakdown
The build process executes the following stages as defined in the Dockerfile:
-
Base Image Selection Dockerfile:1-3
- Uses
debian:stable-slimas the foundation - Sets
DEBIAN_FRONTEND=noninteractiveto prevent prompts during package installation
- Uses
-
System Dependencies Installation Dockerfile:5-12
- Installs:
bash,build-essential,ca-certificates,curl,fossil,tcl - Cleans up apt cache to minimize image size
- Installs:
-
Fossil Repository Cloning Dockerfile:14-17
- Sets working directory to
/src - Executes
fossil clone https://www.sqlite.org/sqllogictest/to download the repository - Opens the fossil file with
fossil opento extract the working tree - Configures default user as
root
- Sets working directory to
-
Extraction Script Creation Dockerfile:19-33
- Switches working directory to
/work - Creates
/usr/local/bin/slt-extractbash script using heredoc syntax - Makes the script executable with
chmod +x
- Switches working directory to
-
Entrypoint Configuration Dockerfile35
- Sets
ENTRYPOINTto executeslt-extractwhen the container runs
- Sets
Sources: Dockerfile:1-36
Running the Extraction
Extraction Command
To extract the test corpus to a local test/ directory:
The --rm flag automatically removes the container after execution. The -v flag mounts the local test/ directory into the container at /work/test.
Sources: README.md:15-19
Volume Mount Mapping
Volume Mapping: Host and Container Filesystem Relationship
Sources: README.md18 Dockerfile:25-28
Extraction Process Flow
Execution Flow: Container Lifecycle and File Extraction
Sources: Dockerfile:20-31 README.md18
Understanding the slt-extract Script
The extraction logic is implemented in the slt-extract bash script, which is embedded directly in the Dockerfile at Dockerfile:20-31
Script Components
| Component | Code Reference | Description |
|---|---|---|
| Shebang | #!/usr/bin/env bash | Specifies bash as the interpreter |
| Error Handling | set -euo pipefail | Exit on error, undefined variables, and pipe failures |
| Source Path | src_root="/src/test" | Location of test corpus in Fossil working tree |
| Destination Path | dest_root="${1:-/work/test}" | Target directory with default value |
| Directory Creation | mkdir -p "$dest_root" | Ensures destination directory exists |
| File Copy | cp -R "$src_root/." "$dest_root/" | Recursively copies all test files |
| Confirmation Output | echo "copied corpus to $dest_root" | Reports successful extraction |
Sources: Dockerfile:20-31
Script Invocation
The script is invoked as the container's entrypoint and accepts an optional argument to override the default destination path:
Sources: Dockerfile25 Dockerfile35
graph TD
TEST["test/"]
EVIDENCE["evidence/\nSQL language specification tests"]
INDEX["index/\nQuery optimization tests"]
EVIDENCE_DDL["DDL tests\nslt_lang_create*, slt_lang_drop*"]
EVIDENCE_DML["DML tests\nslt_lang_update, slt_lang_replace"]
EVIDENCE_DQL["DQL tests\nin1.test, in2.test, slt_lang_aggfunc"]
INDEX_BETWEEN["between/\nBETWEEN operator suites"]
INDEX_OTHER["Other index tests"]
TEST --> EVIDENCE
TEST --> INDEX
EVIDENCE --> EVIDENCE_DDL
EVIDENCE --> EVIDENCE_DML
EVIDENCE --> EVIDENCE_DQL
INDEX --> INDEX_BETWEEN
INDEX --> INDEX_OTHER
Extraction Output Structure
After successful extraction, the test/ directory contains the complete corpus organized into subdirectories:
Extracted Corpus: Directory Structure and Test Categories
For detailed information about test organization and taxonomy, see Test Organization Structure.
Sources: README.md21
Common Usage Patterns
Pattern 1: Fresh Extraction
This is the standard usage pattern for obtaining a clean copy of the corpus:
Sources: README.md:9-19
Pattern 2: Incremental Update
If the Docker image has already been built, you can update the corpus without rebuilding:
Sources: README.md:15-19
Pattern 3: Custom Destination Directory
Extract to a directory other than test/:
The script always writes to /work/test inside the container, but the volume mount determines where files appear on the host.
Sources: Dockerfile25 README.md18
Pattern 4: One-Off Extraction Without Cleanup
If you want to inspect the container without auto-removal:
Note: This pattern is not recommended for regular use as it leaves stopped containers accumulating.
Sources: README.md18
Verification and Validation
Verifying Successful Extraction
After extraction, verify the corpus is present:
Expected output should show hundreds of .test files organized into the evidence/ and index/ subdirectories.
Sources: README.md21
Validating Test File Format
Test files should contain SQL Logic Test directives. Example validation:
For detailed information about test file format, see Test File Format Specification.
Sources: Referenced from overall architecture understanding
Troubleshooting
Issue: Docker Build Fails During Fossil Clone
Symptom: Build fails at the fossil clone step with network errors.
Cause: Network connectivity issues or upstream repository unavailable.
Solution:
Sources: Dockerfile15
Issue: Volume Mount Permissions Error
Symptom: docker run fails with permission denied errors when writing to mounted volume.
Cause: Docker container runs as root but host directory has restricted permissions.
Solution:
Sources: README.md18
Issue: Empty test/ Directory After Extraction
Symptom: Container executes successfully but test/ directory remains empty.
Cause: Incorrect volume mount path or destination parameter mismatch.
Solution:
Sources: Dockerfile25 README.md18
Issue: Fossil Clone Creates Outdated Corpus
Symptom: Extracted tests don't match latest upstream changes.
Cause: Docker layer caching prevents fresh Fossil clone.
Solution:
Sources: Dockerfile:15-17
Issue: Insufficient Disk Space
Symptom: Extraction fails with "no space left on device" error.
Cause: The Fossil repository and extracted tests require significant disk space (typically 1-2 GB).
Solution:
Sources: General Docker troubleshooting knowledge
Build and Extraction Internals
Docker Layer Architecture
The Dockerfile creates multiple layers during the build process:
Docker Build: Layer Caching Strategy
Layers 1-2 are typically cached and reused between builds. Layer 3 (Fossil clone) is invalidated when upstream changes occur or when using --no-cache. Layers 4-7 depend on Layer 3 and will rebuild if Layer 3 changes.
Sources: Dockerfile:1-36
File System Paths Summary
| Path | Location | Purpose |
|---|---|---|
/src | Container | Fossil repository working directory |
/src/sqllogictest.fossil | Container | Fossil repository database file |
/src/test/ | Container | Extracted Fossil working tree |
/work | Container | Container working directory |
/work/test/ | Container | Default extraction destination |
/usr/local/bin/slt-extract | Container | Extraction script executable |
$PWD/test/ | Host | Mounted volume for output |
Sources: Dockerfile14 Dockerfile19 Dockerfile:24-25 Dockerfile20 README.md18
Next Steps
After successfully extracting the corpus:
-
Explore Test Files : Navigate through
test/evidence/andtest/index/to understand test organization. See Test Organization Structure. -
Understand Test Format : Review the SQL Logic Test file format and directives. See Test File Format Specification.
-
Run Tests : Integrate tests into your SQL engine testing workflow. See Integrating with CI/CD Systems.
-
Handle Cross-Database Compatibility : Learn about platform-specific test execution. See Cross-Database Compatibility.
Sources: Overall documentation structure
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Integrating with CI/CD Systems
Relevant source files
Purpose and Scope
This document provides practical guidance for integrating the sqlite-sqllogictest-corpus into continuous integration and deployment pipelines. It covers integration patterns, platform-specific examples, and best practices for running the test corpus as part of automated validation workflows.
For information about how this repository's own automation works to refresh the corpus from upstream, see Automated Update Workflow. For details on building and running the extractor locally, see Building and Running the Extractor.
Sources: .github/workflows/update-corpus.yml:1-45 README.md:1-27
Integration Patterns
The corpus can be integrated into CI/CD systems using three primary patterns, each with different tradeoffs for complexity, freshness, and build time.
graph TB
subgraph "Pattern 1: Direct Clone"
P1_STEP1["git clone\nsqlite-sqllogictest-corpus"]
P1_STEP2["test/ directory\nimmediately available"]
P1_STEP3["Run tests against\nyour SQL engine"]
end
subgraph "Pattern 2: Git Submodule"
P2_STEP1["git submodule add\nsqlite-sqllogictest-corpus"]
P2_STEP2["git submodule update\n--init --recursive"]
P2_STEP3["Reference tests from\nsubmodule path"]
end
subgraph "Pattern 3: Docker Extraction"
P3_STEP1["docker build -t slt-gen\nfrom Dockerfile"]
P3_STEP2["docker run --rm\n-v ./test:/work/test slt-gen"]
P3_STEP3["Extracted test/\ndirectory available"]
end
P1_STEP1 -->
P1_STEP2 --> P1_STEP3
P2_STEP1 -->
P2_STEP2 --> P2_STEP3
P3_STEP1 -->
P3_STEP2 --> P3_STEP3
Pattern Comparison
Pattern 1: Direct Clone
The simplest approach clones this repository directly in the CI pipeline, providing immediate access to the test/ directory.
| Aspect | Details |
|---|---|
| Complexity | Low |
| Corpus Freshness | Depends on clone timing |
| Build Time Impact | ~5-10s for clone |
| Disk Usage | Full repository history |
| Use Case | Quick setup, infrequent testing |
Pattern 2: Git Submodule
Embedding the corpus as a Git submodule allows version pinning and explicit update control.
| Aspect | Details |
|---|---|
| Complexity | Medium |
| Corpus Freshness | Controlled via submodule updates |
| Build Time Impact | ~2-5s for submodule init |
| Disk Usage | Shared with parent repo |
| Use Case | Version stability, reproducible builds |
Pattern 3: Docker Extraction
Building the extraction container pulls the latest corpus directly from the upstream Fossil repository.
| Aspect | Details |
|---|---|
| Complexity | High |
| Corpus Freshness | Always latest from upstream |
| Build Time Impact | ~60-90s for Fossil clone |
| Disk Usage | Container image + extracted tests |
| Use Case | Upstream validation, cutting-edge testing |
Sources: README.md:8-19 .github/workflows/update-corpus.yml:24-31
GitHub Actions Integration
Direct Clone Example
Submodule Example
Docker Extraction Example
This pattern mirrors the workflow defined in .github/workflows/update-corpus.yml:24-31
Sources: .github/workflows/update-corpus.yml:21-31 README.md:8-19
GitLab CI Integration
Direct Clone with Caching
Docker Extraction Pattern
Sources: .github/workflows/update-corpus.yml:24-31 README.md:8-19
Jenkins Pipeline Integration
Declarative Pipeline with Direct Clone
Scripted Pipeline with Docker Extraction
Sources: README.md:8-19 .github/workflows/update-corpus.yml:24-31
CircleCI Integration
Direct Clone Configuration
Docker Extraction with Caching
Sources: README.md:8-19 .github/workflows/update-corpus.yml:24-31
sequenceDiagram
participant CI_Trigger as "CI Trigger\n(push/PR/schedule)"
participant CI_Runner as "CI Runner\n(GitHub Actions/GitLab/etc)"
participant Git_Clone as "git clone/checkout"
participant Docker_Build as "docker build -t slt-gen"
participant Docker_Run as "docker run slt-gen"
participant Test_Dir as "test/ directory"
participant Test_Runner as "your-test-runner"
participant Results as "Test Results"
CI_Trigger->>CI_Runner: Trigger build
CI_Runner->>Git_Clone: Clone corpus repo
Git_Clone-->>CI_Runner: Repository available
alt "Docker Extraction Pattern"
CI_Runner->>Docker_Build: Build extraction image
Docker_Build-->>CI_Runner: Image slt-gen ready
CI_Runner->>Docker_Run: Extract to test/
Docker_Run->>Test_Dir: Write test files
Test_Dir-->>Docker_Run: Extraction complete
else "Direct Clone Pattern"
Git_Clone->>Test_Dir: test/ from repository
end
CI_Runner->>Test_Runner: Execute tests
Test_Runner->>Test_Dir: Read test files
Test_Dir-->>Test_Runner: Test content
Test_Runner->>Test_Runner: Run against SQL engine
Test_Runner-->>Results: Pass/Fail status
Results-->>CI_Runner: Report results
CI/CD Execution Flow
The following diagram illustrates the typical execution flow when integrating the corpus into a CI/CD pipeline, showing the relationship between CI platform components and the corpus extraction process.
Sources: .github/workflows/update-corpus.yml:21-44 README.md:8-19
Best Practices
Caching Strategies
Repository-Level Caching
Cache the cloned repository to avoid re-downloading on every build:
Docker Layer Caching
When using the Docker extraction pattern, enable Docker layer caching to reuse the Fossil clone step:
Sources: .github/workflows/update-corpus.yml:24-31
Selective Test Execution
Rather than running all tests, filter by category based on what your SQL engine implements:
For details on test organization, see Test Organization Structure.
Sources: Based on test directory structure patterns
Update Frequency Considerations
| Update Pattern | Pros | Cons | Recommended For |
|---|---|---|---|
| On every CI run | Always latest tests | Slower builds, potential instability | Upstream compatibility validation |
| Weekly scheduled | Fresh corpus, predictable | May miss urgent updates | Regular conformance testing |
| Manual trigger | Full control, stable | May become stale | Release validation |
| Pinned version | Reproducible, stable | Requires manual updates | Production validation |
Implementing Update Frequency
The repository's own update workflow uses weekly scheduling as seen in .github/workflows/update-corpus.yml:4-6
Sources: .github/workflows/update-corpus.yml:4-8
Handling Cross-Platform Tests
When running tests against non-SQLite databases, account for platform-specific directives:
For details on conditional directives, see Conditional Execution Directives.
Sources: Based on cross-platform test patterns described in high-level diagrams
Test Execution Strategies
Complete Corpus Validation
Parallel Execution
Smoke Test Strategy
Run a subset of critical tests for quick validation:
Sources: Based on test organization patterns
Integration Checklist
Before integrating the corpus into your CI/CD pipeline, verify:
- Test runner compatibility : Your test runner can parse the SQL Logic Test format (see Test File Format Specification)
- Database platform : Set appropriate
DATABASE_ENGINEidentifier for conditional directives (see Conditional Execution Directives) - Resource allocation : CI runner has sufficient disk space (~500MB for corpus) and memory for test execution
- Execution time : Budget appropriate time (full corpus may take 30+ minutes depending on SQL engine)
- Caching strategy : Implement caching to avoid redundant downloads/builds
- Update frequency : Decide on corpus refresh schedule (weekly, manual, or per-build)
- Failure handling : Configure whether test failures should block deployment
- Result reporting : Ensure test results are captured and reported in CI logs/artifacts
Sources: General best practices derived from workflow patterns
Common Pitfalls
Permission Issues with Docker Volumes
When using the Docker extraction pattern, the container writes files as root. Add cleanup or permission fixes:
Fossil Clone Timeouts
The Docker build includes a Fossil clone which can timeout on slow networks. Increase Docker build timeout:
Stale Cache with Direct Clone
When using repository caching, the cache key must invalidate when upstream changes:
Sources: README.md:15-18 .github/workflows/update-corpus.yml:28-31
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Test File Format Specification
Relevant source files
- test/evidence/in1.test
- test/evidence/slt_lang_createtrigger.test
- test/index/between/1/slt_good_0.test
- test/index/between/10/slt_good_1.test
Purpose and Scope
This document provides the technical specification for the SQL Logic Test (SLT) file format used throughout the test corpus. Test files in this repository follow a line-based domain-specific language (DSL) that defines SQL statements, expected results, and conditional execution directives. This format enables cross-database testing and precise validation of SQL behavior.
For information about the test corpus organization and categories, see Test Corpus Reference. For practical usage examples, see Usage Guide.
File Structure Overview
SQL Logic Test files are plain text files with a .test extension organized in a line-based format where each directive occupies one or more consecutive lines. The parser processes files sequentially, interpreting directives and executing SQL statements against the target database.
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_createtrigger.test:1-220 test/index/between/1/slt_good_0.test:1-768
graph TB FILE["Test File (.test)"] --> LINES["Line-by-Line Processing"] LINES --> COMMENT["# Comment Lines"] LINES --> CONFIG["Configuration Directives"] LINES --> CONTROL["Control Flow Directives"] LINES --> EXEC["Execution Directives"] LINES --> BLANK["Blank Lines (ignored)"] CONFIG --> HASH["hash-threshold N"] CONTROL --> SKIPIF["skipif <database>"] CONTROL --> ONLYIF["onlyif <database>"] CONTROL --> HALT["halt"] EXEC --> STATEMENT["statement ok/error"] EXEC --> QUERY["query <type> <mode> [label-N]"] STATEMENT --> SQL1["SQL Statement"] QUERY --> SQL2["SQL Query"] QUERY --> EXPECTED["---- separator"] QUERY --> RESULTS["Expected Results"]
Comment Syntax
Lines beginning with # are treated as comments and ignored during execution. Comments provide documentation and reference SQLite evidence documentation identifiers.
# This is a comment
# EVIDENCE-OF: R-52275-55503 When the right operand is an empty set...
Sources: test/evidence/in1.test:1-20
Configuration Directives
hash-threshold
The hash-threshold directive sets the number of result rows after which the test runner should use hash-based comparison instead of direct string matching. This optimizes performance for large result sets.
hash-threshold 8
Syntax:
hash-threshold <N>where N is an integer
Sources: test/evidence/slt_lang_createtrigger.test1 test/index/between/1/slt_good_0.test1
Control Flow Directives
Control flow directives enable conditional test execution based on the target database platform, allowing a single test file to support multiple SQL engines.
flowchart TD
START["Parse Directive"] --> CHECK_TYPE{"Directive Type?"}
CHECK_TYPE -->|skipif| SKIPIF["skipif <database>"]
CHECK_TYPE -->|onlyif| ONLYIF["onlyif <database>"]
CHECK_TYPE -->|halt| HALT["halt"]
SKIPIF --> MATCH_SKIP{"Current DB\nmatches?"}
ONLYIF --> MATCH_ONLY{"Current DB\nmatches?"}
HALT --> MATCH_HALT{"Current DB\nmatches?"}
MATCH_SKIP -->|Yes| SKIP["Skip following statements\nuntil next directive"]
MATCH_SKIP -->|No| CONTINUE1["Continue execution"]
MATCH_ONLY -->|No| SKIP
MATCH_ONLY -->|Yes| CONTINUE2["Continue execution"]
MATCH_HALT -->|Yes| STOP["Stop file execution"]
MATCH_HALT -->|No| CONTINUE3["Continue execution"]
SKIP --> NEXT["Next directive"]
CONTINUE1 --> NEXT
CONTINUE2 --> NEXT
CONTINUE3 --> NEXT
skipif
The skipif directive causes the test runner to skip the immediately following SQL statement(s) if the current database matches the specified platform.
Syntax:
skipif <database>
<SQL statement or query>
Supported database identifiers:
sqlite- SQLitemysql- MySQL/MariaDBmssql- Microsoft SQL Serveroracle- Oracle Databasepostgresql- PostgreSQL
Example:
skipif mysql
statement ok
CREATE TABLE t7(a TEXT UNIQUE)
Sources: test/evidence/in1.test:398-404
onlyif
The onlyif directive causes the test runner to execute the following statement(s) only if the current database matches the specified platform. All other platforms skip the statement.
Syntax:
onlyif <database>
<SQL statement or query>
Example:
onlyif sqlite
query I nosort
SELECT 1 IN ()
----
0
Sources: test/evidence/in1.test:22-26 test/evidence/slt_lang_createtrigger.test:18-19
halt
The halt directive, when preceded by an onlyif directive, causes test execution to stop entirely for the specified database. This is commonly used to exclude entire test files from certain platforms.
Syntax:
onlyif <database>
halt
Example:
# Skip this entire file if MS SQL Server
onlyif mssql
halt
Sources: test/evidence/in1.test:3-9 test/evidence/slt_lang_createtrigger.test:18-19
graph LR
STMT["statement"] --> TYPE{"Expected\nOutcome?"}
TYPE -->|ok| OK["statement ok"]
TYPE -->|error| ERROR["statement error"]
OK --> SQL1["SQL Statement\n(must succeed)"]
ERROR --> SQL2["SQL Statement\n(must fail)"]
SQL1 --> EXEC1["Execute Statement"]
SQL2 --> EXEC2["Execute Statement"]
EXEC1 --> CHECK1{"Succeeded?"}
EXEC2 --> CHECK2{"Failed?"}
CHECK1 -->|Yes| PASS1["Test Passes"]
CHECK1 -->|No| FAIL1["Test Fails"]
CHECK2 -->|Yes| PASS2["Test Passes"]
CHECK2 -->|No| FAIL2["Test Fails"]
Statement Execution Directives
Statement directives execute SQL DDL, DML, or other non-query statements and validate their success or failure.
statement ok
The statement ok directive indicates that the following SQL statement must execute successfully without error.
Syntax:
statement ok
<SQL statement>
Example:
statement ok
CREATE TABLE t1(x INTEGER)
Sources: test/evidence/in1.test:66-67 test/evidence/slt_lang_createtrigger.test:3-16
statement error
The statement error directive indicates that the following SQL statement must fail with an error. This validates error handling and constraint enforcement.
Syntax:
statement error
<SQL statement>
Example:
# Already exists
statement error
CREATE TRIGGER t1r1 UPDATE ON t1 BEGIN SELECT 1; END;
Sources: test/evidence/slt_lang_createtrigger.test:27-29
graph TB QUERY["query Directive"] --> FORMAT["Query Type\nSpecification"] FORMAT --> MODE["Comparison Mode"] MODE --> LABEL["Optional Label"] FORMAT --> TYPES["Type Codes"] TYPES --> I["I = Integer"] TYPES --> II["II = Two Integers"] TYPES --> III["III = Three Integers"] TYPES --> T["T = Text"] TYPES --> R["R = Real (Float)"] MODE --> NOSORT["nosort\n(order-dependent)"] MODE --> ROWSORT["rowsort\n(order-independent)"] LABEL --> MARKER["label-N\n(groups related tests)"] QUERY --> SEPARATOR["----\n(separates query from results)"] SEPARATOR --> RESULTS["Expected Results\n(one value per line)"]
Query Execution Directives
Query directives execute SELECT statements and validate their results against expected output.
Query Type Specification
The query type code immediately follows the query keyword and specifies the expected column types in the result set.
Syntax:
query <type-code> <comparison-mode> [label-<N>]
<SQL SELECT statement>
----
<expected results>
Type Codes:
| Code | Description | Example |
|---|---|---|
I | Single integer column | query I nosort |
II | Two integer columns | query II rowsort |
III | Three integer columns | query III nosort |
T | Text/string column | query T rowsort |
R | Real/float column | query R nosort |
Multiple type codes can be combined to specify multiple columns with different types.
Sources: test/evidence/in1.test:23-26 test/index/between/1/slt_good_0.test:69-107
Comparison Modes
nosort
The nosort mode performs order-dependent comparison. Result rows must match the expected output in exact sequence.
Example:
query I nosort
SELECT 1 IN (2)
----
0
Sources: test/evidence/in1.test:28-31
rowsort
The rowsort mode performs order-independent comparison. Both actual and expected results are sorted before comparison, allowing queries to return rows in any order.
Example:
query I rowsort label-0
SELECT pk FROM tab0 WHERE col3 < 6 OR col3 < 1 AND col3 BETWEEN 2 AND 7
----
Sources: test/index/between/1/slt_good_0.test:69-72
Label Markers
Label markers group related test cases that verify the same SQL behavior with different table configurations or query formulations. Tests with the same label should produce identical results.
Syntax:
query <type> <mode> label-<N>
Where <N> is a numeric identifier.
Example - Grouped Tests:
query I nosort label-1
SELECT 1 IN t1
----
0
query I nosort label-1
SELECT 1 IN (SELECT * FROM t1)
----
0
Both queries are label-1 and must return the same result (0), demonstrating that table syntax and (SELECT * FROM table) syntax are equivalent.
Sources: test/evidence/in1.test:69-78
Expected Output Format
Result Separator
The ---- line separates the SQL query from its expected results. This delimiter is mandatory for all query directives.
Example:
query I nosort
SELECT 1 IN (2,3,4)
----
0
Result Values
Expected values appear one per line after the ---- separator. For queries returning multiple columns, values appear in sequence:
Single column:
query I nosort
SELECT 1
----
1
Multiple rows:
query I nosort
SELECT * FROM (VALUES (1), (2), (3))
----
1
2
3
Multiple columns:
query II nosort
SELECT 1, 2
----
1
2
Empty Results
When a query returns no rows, the expected output section remains empty:
query I rowsort label-0
SELECT pk FROM tab0 WHERE col3 < 6 OR col3 < 1 AND col3 BETWEEN 2 AND 7
----
Sources: test/index/between/1/slt_good_0.test:69-84
NULL Values
NULL values in result sets are represented as empty lines or the literal text NULL depending on the test configuration:
query I nosort
SELECT null IN ()
----
0
Sources: test/evidence/in1.test:54-64
Test File Processing Flow
Sources: test/evidence/in1.test:1-1156 test/index/between/1/slt_good_0.test:1-768
graph TB
TEST["Test Case"] --> VARIANT{"Database-Specific\nSyntax?"}
VARIANT -->|No| COMMON["Common test\n(no directives)"]
VARIANT -->|Yes| PLATFORM["Platform-specific variants"]
PLATFORM --> SQLITE["onlyif sqlite\nquery I nosort\nSELECT 1 IN ()\n----\n0"]
PLATFORM --> MYSQL["skipif mysql\nstatement ok\nCREATE TABLE t7(a TEXT UNIQUE)"]
PLATFORM --> ALT_MYSQL["onlyif mysql\nstatement ok\nCREATE TABLE t7(a TEXT, UNIQUE (a(1)))"]
COMMON --> ALL["Executed on\nall databases"]
SQLITE --> ONLY_SQLITE["Executed only\non SQLite"]
MYSQL --> NOT_MYSQL["Skipped on MySQL"]
ALT_MYSQL --> ONLY_MYSQL["Executed only\non MySQL"]
style COMMON fill:#e1f5ff
style SQLITE fill:#fff4e1
style MYSQL fill:#ffe1e1
style ALT_MYSQL fill:#ffe1e1
Cross-Database Compatibility Patterns
Test files use combinations of conditional directives to handle platform-specific SQL syntax differences:
Common patterns:
- Feature availability:
onlyif sqlite # empty RHS
query I nosort
SELECT 1 IN ()
----
0
- Syntax variations:
skipif mysql
statement ok
CREATE TABLE t7(a TEXT UNIQUE)
onlyif mysql
statement ok
CREATE TABLE t7(a TEXT, UNIQUE (a(1)))
- Platform exclusion:
onlyif mssql
halt
Sources: test/evidence/in1.test:3-9 test/evidence/in1.test:22-26 test/evidence/in1.test:398-427
Directive Precedence and Scope
Scope rules:
| Directive | Scope | Duration |
|---|---|---|
hash-threshold | File-wide | Entire file |
onlyif/halt | File-wide (when halt) | Remainder of file |
skipif | Statement | Next SQL statement only |
onlyif | Statement | Next SQL statement only |
statement | Statement | Immediate |
query | Statement | Immediate |
# comment | Line | Line only |
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_createtrigger.test:1-220
Advanced Query Features
BETWEEN Operator Testing
Test files extensively use BETWEEN expressions and validate their equivalence to range comparisons:
query I rowsort label-0
SELECT pk FROM tab0 WHERE col3 BETWEEN 2 AND 7
----
query I rowsort label-0
SELECT pk FROM tab0 WHERE (col3 >= 2 AND col3 <= 7)
----
Both queries share label-0 and must produce identical results, demonstrating BETWEEN semantics.
Sources: test/index/between/1/slt_good_0.test:69-84
Subquery Equivalence
Tests validate that different query formulations produce identical results:
onlyif sqlite
query I nosort label-1
SELECT 1 IN t1
----
0
query I nosort label-1
SELECT 1 IN (SELECT * FROM t1)
----
0
The label-1 marker groups these tests, asserting that SQLite's table-as-operand syntax (IN t1) is equivalent to the standard subquery syntax.
Sources: test/evidence/in1.test:69-90
Complex WHERE Clauses
Test files include deeply nested WHERE clauses to validate parser correctness and operator precedence:
query I rowsort label-50
SELECT pk FROM tab0 WHERE ((((((col0 >= 3 OR ((col4 > 5.3) OR col0 = 1))
OR (((((((((((col0 IN (6,8,8) AND (((col3 IS NULL)) OR (col3 >= 7))...
These tests ensure databases correctly parse and execute complex boolean expressions.
Sources: test/index/between/1/slt_good_0.test:289-337
File Naming Conventions
Test files follow consistent naming patterns:
| Pattern | Description | Example |
|---|---|---|
slt_lang_*.test | SQL language feature tests | slt_lang_createtrigger.test |
in1.test, in2.test | IN/NOT IN operator tests | in1.test |
slt_good_*.test | Index optimization tests | slt_good_0.test |
between/<N>/ | BETWEEN operator with N records | between/1/, between/10/ |
Sources: test/evidence/slt_lang_createtrigger.test1 test/evidence/in1.test1 test/index/between/1/slt_good_0.test1
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Test Directives Reference
Relevant source files
Purpose and Scope
This page provides a complete reference for all test directives used in SQL Logic Test files within the sqlite-sqllogictest-corpus. Test directives are line-based commands that control test execution, define SQL statements and queries, specify expected results, and enable conditional execution across different database platforms.
For information about result comparison modes (nosort, rowsort, valuesort) and expected output format, see Result Comparison Modes. For the broader context of how test files are organized and executed, see Test File Format Specification.
Directive Categories
SQL Logic Test files use a domain-specific language (DSL) with several categories of directives:
| Category | Purpose | Examples |
|---|---|---|
| Conditional Execution | Control which database platforms execute specific tests | skipif, onlyif, halt |
| Statement Execution | Execute SQL statements without returning results | statement ok, statement error |
| Query Execution | Execute SQL queries and validate results | query I, query IT, query III |
| Test Configuration | Configure test execution parameters | hash-threshold, labels |
| Documentation | Provide human-readable context | # comments |
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_replace.test:1-76
Conditional Execution Directives
Overview
Conditional execution directives enable a single test file to support multiple database platforms by controlling which statements execute based on the current database engine. These directives are essential for cross-platform compatibility.
Diagram: Conditional Directive Execution Flow
stateDiagram-v2
[*] --> ParseDirective : Read line
ParseDirective --> SkipIf : skipif <db>
ParseDirective --> OnlyIf : onlyif <db>
ParseDirective --> Halt : halt
ParseDirective --> Execute : Other directive
SkipIf --> CheckDB1 : Check if current DB matches
OnlyIf --> CheckDB2 : Check if current DB matches
Halt --> CheckDB3 : Check if previous onlyif matched
CheckDB1 --> SkipNext : Match - Skip next statement(s)
CheckDB1 --> Execute : No match - Continue
CheckDB2 --> Execute : Match - Continue
CheckDB2 --> SkipNext : No match - Skip next statement(s)
CheckDB3 --> [*] : Previous onlyif matched - Stop file
CheckDB3 --> Execute : Previous onlyif didn't match - Continue
SkipNext --> ParseDirective
Execute --> ParseDirective
Sources: test/evidence/in1.test:4-9 test/evidence/slt_lang_replace.test:2-7
skipif Directive
Syntax: skipif <database>
Purpose: Skip the next statement or query if the current database platform matches the specified database.
Supported Database Identifiers:
sqlite- SQLite database enginemysql- MySQL/MariaDBmssql- Microsoft SQL Serveroracle- Oracle Databasepostgresql- PostgreSQL
Example:
skipif mysql
statement ok
CREATE TABLE t7(a TEXT UNIQUE)
In this example, the CREATE TABLE statement is skipped when running on MySQL because MySQL requires a key length specification for TEXT columns in UNIQUE constraints.
Sources: test/evidence/in1.test:398-400 test/evidence/slt_lang_replace.test:37-39
onlyif Directive
Syntax: onlyif <database>
Purpose: Execute the next statement or query only if the current database platform matches the specified database. All other platforms skip the statement.
Example:
onlyif sqlite
query I nosort
SELECT 1 IN ()
----
0
This query tests SQLite-specific behavior where empty lists are allowed on the right-hand side of IN operators, which is not standard SQL and not supported by most other databases.
Usage Pattern for File-Level Exclusion:
onlyif mssql
halt
onlyif oracle
halt
This pattern at the beginning of a file causes the entire test file to halt execution when running on MSSQL or Oracle, effectively excluding these platforms from the test.
Sources: test/evidence/in1.test:4-9 test/evidence/in1.test:22-26 test/evidence/slt_lang_replace.test:2-7
halt Directive
Syntax: halt
Purpose: Immediately stop execution of the current test file. Typically used in combination with onlyif to exclude entire files from specific database platforms.
Common Pattern:
# skip this entire file if ms sql server
onlyif mssql
halt
# skip this entire file if oracle
onlyif oracle
halt
This pattern appears at the beginning of test files that contain database-specific features not supported by certain platforms.
Sources: test/evidence/in1.test:4-9 test/evidence/slt_lang_replace.test:1-7
Statement Execution Directives
statement ok
Syntax: statement ok
Purpose: Execute the following SQL statement and expect it to complete successfully without errors. The statement should not return results (typically DDL or DML statements).
Example:
statement ok
CREATE TABLE t1(x INTEGER)
statement ok
INSERT INTO t4 VALUES(2)
statement ok
INSERT INTO t5 SELECT * FROM t4
The directive is followed by one or more lines containing the SQL statement to execute. The statement may span multiple lines until the next directive is encountered.
Sources: test/evidence/in1.test:66-67 test/evidence/in1.test:369-381 test/evidence/slt_lang_replace.test:11-19
statement error
Syntax: statement error [optional-error-pattern]
Purpose: Execute the following SQL statement and expect it to fail with an error. Optionally, verify that the error message matches a specific pattern.
Note: While statement error is part of the SQL Logic Test specification, it does not appear in the provided test file examples. The corpus primarily uses statement ok for validation.
Query Execution Directives
Query Directive Syntax
General Syntax: query <type-spec> <sort-mode> [label-<id>]
Components:
| Component | Purpose | Required |
|---|---|---|
type-spec | Defines expected column types in result | Yes |
sort-mode | Specifies result ordering validation | Optional |
label-<id> | Groups related queries for validation | Optional |
Type Specifications
The type specification defines the number and types of columns expected in the query result:
| Type Code | Meaning | Example |
|---|---|---|
I | Single integer column | query I |
II | Two integer columns | query II |
III | Three integer columns | query III |
T | Single text/string column | query T |
IT | Integer column, then text column | query IT |
R | Single real/floating-point column | query R |
Example:
query I nosort
SELECT 1 IN (2,3,4)
----
0
query IT rowsort
SELECT x, y FROM t1 WHERE x=2
----
2
insert
Sources: test/evidence/in1.test:23-26 test/evidence/slt_lang_replace.test:24-35
Sort Mode Specifications
Sort modes control how actual results are compared with expected results:
| Sort Mode | Behavior |
|---|---|
nosort | Results must match in exact order |
rowsort | Results are sorted before comparison (order-independent) |
valuesort | Individual values are sorted (less common) |
For detailed information about sort modes and result comparison, see Result Comparison Modes.
Sources: test/evidence/in1.test:23-26 test/evidence/slt_lang_replace.test:24-26
Label Markers
Syntax: query <type> <sort-mode> label-<identifier>
Purpose: Group related queries that test the same logical condition across different implementation approaches. Queries with the same label should produce identical results.
Example:
onlyif sqlite
query I nosort label-1
SELECT 1 IN t1
----
0
query I nosort label-1
SELECT 1 IN (SELECT * FROM t1)
----
0
Both queries are marked with label-1, indicating they test the same logical condition: checking if 1 is in an empty table. The first uses SQLite's table-as-operand syntax, while the second uses standard subquery syntax. Both must produce result 0.
Sources: test/evidence/in1.test:70-78
Test Configuration Directives
hash-threshold
Syntax: hash-threshold <number>
Purpose: Configure the hash threshold for result validation. When the number of result rows exceeds this threshold, the test runner may use hash-based comparison instead of line-by-line comparison for performance.
Example:
hash-threshold 8
This sets the hash threshold to 8 rows. Results with more than 8 rows may be validated using MD5 or other hash comparison.
Sources: test/evidence/slt_lang_replace.test9
Comments
Syntax: # comment text
Purpose: Provide human-readable documentation within test files. Comments are ignored during execution.
Common Uses:
- Document test purpose or evidence citations
- Explain platform-specific behavior
- Provide context for complex test scenarios
Example:
# EVIDENCE-OF: R-52275-55503 When the right operand is an empty set, the
# result of IN is false and the result of NOT IN is true, regardless of
# the left operand and even if the left operand is NULL.
# skip this entire file if ms sql server
onlyif mssql
halt
Sources: test/evidence/in1.test:1-19 test/evidence/slt_lang_replace.test:1-3
flowchart TD
START["Test file line"] --> PARSE["Parse directive type"]
PARSE --> COMMENT["# comment"]
PARSE --> SKIPIF["skipif <db>"]
PARSE --> ONLYIF["onlyif <db>"]
PARSE --> HALT["halt"]
PARSE --> STMT_OK["statement ok"]
PARSE --> QUERY["query <type> <mode>"]
PARSE --> HASH["hash-threshold <n>"]
COMMENT --> NEXT1["Continue to next line"]
HASH --> NEXT2["Set threshold, continue"]
SKIPIF --> CHECK_SKIP{"Current DB\nmatches?"}
ONLYIF --> CHECK_ONLY{"Current DB\nmatches?"}
HALT --> STOP["Stop file execution"]
CHECK_SKIP -->|Yes| SKIP_BLOCK["Skip next SQL block"]
CHECK_SKIP -->|No| NEXT3["Continue to next line"]
CHECK_ONLY -->|Yes| NEXT4["Continue to next line"]
CHECK_ONLY -->|No| SKIP_BLOCK
SKIP_BLOCK --> NEXT5["Continue to next line"]
STMT_OK --> READ_SQL1["Read SQL statement"]
QUERY --> READ_SQL2["Read SQL query"]
READ_SQL1 --> EXEC_STMT["Execute statement"]
READ_SQL2 --> EXEC_QUERY["Execute query"]
EXEC_STMT --> VERIFY_OK{"Execution\nsucceeded?"}
EXEC_QUERY --> READ_EXPECTED["Read expected results"]
VERIFY_OK -->|Yes| NEXT6["Continue to next line"]
VERIFY_OK -->|No| FAIL1["Test failure"]
READ_EXPECTED --> COMPARE["Compare actual vs expected"]
COMPARE --> MATCH{"Results\nmatch?"}
MATCH -->|Yes| NEXT7["Continue to next line"]
MATCH -->|No| FAIL2["Test failure"]
NEXT1 --> END["Next directive"]
NEXT2 --> END
NEXT3 --> END
NEXT4 --> END
NEXT5 --> END
NEXT6 --> END
NEXT7 --> END
Directive Execution Model
The following diagram maps test directives to the actual execution behavior in test runners:
Diagram: Test Directive Execution State Machine
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_replace.test:1-76
Platform Compatibility Matrix
The following table summarizes how conditional directives are used across different database platforms:
| Feature/Syntax | SQLite | MySQL | MSSQL | Oracle | PostgreSQL |
|---|---|---|---|---|---|
Empty RHS in IN clause () | ✓ (onlyif sqlite) | ✗ (skipif mysql) | ✗ (halt on onlyif) | ✗ (halt on onlyif) | ✗ |
Table-as-operand syntax IN t1 | ✓ (onlyif sqlite) | ✗ | ✗ | ✗ | ✗ |
INSERT OR REPLACE | ✓ | ✗ (skipif mysql) | ✗ (halt on onlyif) | ✗ (halt on onlyif) | ✗ |
REPLACE INTO | ✓ | ✓ | ✗ (halt on onlyif) | ✗ (halt on onlyif) | ✗ |
| TEXT UNIQUE without length | ✓ | ✗ (skipif mysql) | Varies | Varies | ✓ |
Sources: test/evidence/in1.test:4-9 test/evidence/in1.test:22-26 test/evidence/in1.test:69-73 test/evidence/slt_lang_replace.test:1-7 test/evidence/slt_lang_replace.test:37-46
Complete Directive Reference Table
| Directive | Syntax | Purpose | Affects Next |
|---|---|---|---|
skipif | skipif <db> | Skip next statement if DB matches | 1 statement/query |
onlyif | onlyif <db> | Execute next statement only if DB matches | 1 statement/query |
halt | halt | Stop file execution immediately | Entire file |
statement ok | statement ok | Execute SQL statement expecting success | SQL block |
statement error | statement error [pattern] | Execute SQL statement expecting failure | SQL block |
query | query <type> [mode] [label-N] | Execute query and validate results | SQL query + results |
hash-threshold | hash-threshold <n> | Set result hashing threshold | Global setting |
# | # comment | Documentation comment | None |
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_replace.test:1-76
graph TB
subgraph "Directive Categories"
COND["Conditional Directives\nskipif/onlyif/halt"]
STMT["Statement Directives\nstatement ok"]
QUERY["Query Directives\nquery I/IT/etc"]
CONFIG["Configuration\nhash-threshold"]
end
subgraph "Test File Examples"
IN1["test/evidence/in1.test\nLines 4-9, 22-26, 66-78"]
REPLACE["test/evidence/slt_lang_replace.test\nLines 1-9, 24-55"]
end
subgraph "Platform-Specific Behaviors"
SQLITE_ONLY["SQLite-only features\nEmpty IN lists\nTable-as-operand"]
MYSQL_SKIP["MySQL exclusions\nTEXT UNIQUE length\nINSERT OR REPLACE"]
MSSQL_HALT["MSSQL/Oracle exclusions\nFile-level halt"]
end
COND --> IN1
COND --> REPLACE
STMT --> IN1
STMT --> REPLACE
QUERY --> IN1
QUERY --> REPLACE
CONFIG --> REPLACE
IN1 --> SQLITE_ONLY
IN1 --> MYSQL_SKIP
IN1 --> MSSQL_HALT
REPLACE --> MYSQL_SKIP
REPLACE --> MSSQL_HALT
Cross-Reference: Code to Test Directives
The following diagram shows how test directives in the corpus map to actual test files and validation scenarios:
Diagram: Test Directive Usage in Corpus Files
Sources: test/evidence/in1.test:1-1156 test/evidence/slt_lang_replace.test:1-76
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Result Comparison Modes
Relevant source files
- test/evidence/slt_lang_aggfunc.test
- test/index/between/10/slt_good_1.test
- test/index/between/10/slt_good_5.test
Purpose and Scope
This document describes the result comparison modes used in SQL Logic Test files to validate query outputs. These modes determine how the test runner compares actual query results against expected values, enabling flexible and robust testing of SQL behavior across different scenarios.
For information about test directives and query type specifications, see Test Directives Reference. For the overall test file format, see Test File Format Specification.
Overview
Result comparison modes control how query results are validated in SQL Logic Test files. The mode is specified as part of the query directive and determines whether results must match exactly in order, can be compared order-independently, or are hashed for large result sets.
Comparison Mode Syntax
Query directives follow this pattern:
query <type> <mode> [label-<name>]
Where:
<type>specifies the column data types (I, T, R, II, III, etc.)<mode>specifies the comparison mode (nosort,rowsort, or omitted for hash)label-<name>is an optional marker for grouping related tests
Sources: test/evidence/slt_lang_aggfunc.test:28-29 test/index/between/10/slt_good_1.test:90-91
Comparison Modes
Mode: nosort
The nosort mode requires that query results match the expected output in exact order. The database must return rows in the precise sequence specified in the test file.
Example Usage
query I nosort
SELECT count(DISTINCT x) FROM t1
----
2
flowchart TD
Query["query I nosort"] --> Execute["Execute SQL Query"]
Execute --> ActualResults["Actual Results:\nRow 1, Row 2, Row 3"]
ExpectedResults["Expected Results:\nRow 1, Row 2, Row 3"] --> Compare
ActualResults --> Compare["Compare Results\nOrder-Sensitive"]
Compare --> Match{"Exact\nMatch?"}
Match -->|Yes| Pass["Test Pass"]
Match -->|No| Fail["Test Fail"]
In this example, the query must return the single value 2. Since it's a single-value result, order is trivial, but for multi-row results, the order must match exactly.
Diagram: nosort Comparison Flow
Use Cases:
- Testing ORDER BY clauses where sequence matters
- Validating aggregation functions that return specific ordered results
- Ensuring deterministic query execution order
Sources: test/evidence/slt_lang_aggfunc.test:28-31 test/evidence/slt_lang_aggfunc.test:173-177
Mode: rowsort
The rowsort mode performs order-independent comparison. Both actual and expected results are sorted before comparison, allowing tests to validate result sets regardless of the order in which rows are returned.
Example Usage
query I rowsort label-0
SELECT pk FROM tab0 WHERE col4 BETWEEN 57.93 AND 43.23 OR ((col3 > 27) AND (col3 >= 59))
----
0
3
5
7
9
In this example, the database can return the primary keys 0, 3, 5, 7, 9 in any order, and the test will pass as long as the set of values matches.
Diagram: rowsort Comparison Flow
flowchart TD
Query["query I rowsort"] --> Execute["Execute SQL Query"]
Execute --> ActualResults["Actual Results:\n3, 0, 9, 5, 7"]
ExpectedResults["Expected Results:\n0, 3, 5, 7, 9"] --> SortExpected["Sort Expected"]
ActualResults --> SortActual["Sort Actual"]
SortActual --> SortedActual["Sorted Actual:\n0, 3, 5, 7, 9"]
SortExpected --> SortedExpected["Sorted Expected:\n0, 3, 5, 7, 9"]
SortedActual --> Compare["Compare Sorted Results"]
SortedExpected --> Compare
Compare --> Match{"Match?"}
Match -->|Yes| Pass["Test Pass"]
Match -->|No| Fail["Test Fail"]
Use Cases:
- Testing queries without ORDER BY where row order is undefined
- Validating set operations (UNION, INTERSECT, etc.)
- Comparing results from indexed vs. non-indexed queries
- Testing BETWEEN operator behavior across different table structures
Sources: test/index/between/10/slt_good_1.test:90-97 test/index/between/10/slt_good_1.test:230-239
Hash-Based Comparison
For large result sets, tests use hash-based comparison to avoid storing thousands of expected rows. The expected output specifies a count and hash value instead of individual rows.
Example Usage
query I rowsort label-10
SELECT pk FROM tab0 WHERE col0 < 72 AND ((col3 >= 83 ...
----
9 values hashing to 0b2f3ce47428ebec5f2931eddc864093
flowchart LR
Query["query I rowsort label-10"] --> Execute["Execute SQL Query"]
Execute --> Count["Count Rows"]
Execute --> Hash["Hash Row Values"]
Count --> CountResult["Result: 9 rows"]
Hash --> HashResult["Result: 0b2f3ce..."]
Expected["Expected:\n9 values hashing to\n0b2f3ce..."] --> CompareCount
CountResult --> CompareCount["Compare Count"]
HashResult --> CompareHash["Compare Hash"]
Expected --> CompareHash
CompareCount --> Match{"Both\nMatch?"}
CompareHash --> Match
Match -->|Yes| Pass["Test Pass"]
Match -->|No| Fail["Test Fail"]
The test validates that:
- The query returns exactly 9 rows
- The rows hash to the specified value
0b2f3ce47428ebec5f2931eddc864093
Diagram: Hash-Based Comparison Flow
Use Cases:
- Complex queries with hundreds or thousands of result rows
- Index optimization tests where the full result set is large
- Queries with deeply nested conditions
Hash Threshold: Tests may specify a hash threshold at the file level:
hash-threshold 8
This indicates that result sets with more than 8 values will use hash-based comparison.
Sources: test/index/between/10/slt_good_1.test1 test/index/between/10/slt_good_1.test:181-183 test/evidence/slt_lang_aggfunc.test1
Label Markers
Label markers provide semantic grouping for related tests and serve as documentation within test files. They follow the pattern label-<identifier>.
Common Label Patterns
| Label Pattern | Purpose | Example |
|---|---|---|
label-NULL | Tests validating NULL handling | query I nosort label-NULL |
label-sum | Tests for sum() aggregate function | query I nosort label-sum |
label-zero | Tests expecting zero results | query I nosort label-zero |
label-<number> | Numeric grouping for test suites | label-0, label-10, label-20 |
Example Usage
query I nosort label-NULL
SELECT avg(x) FROM t1 WHERE y='null'
----
NULL
query I nosort label-sum
SELECT sum(x) FROM t1
----
5
query I nosort label-zero
SELECT total(x) FROM t1 WHERE y='null'
----
0
Benefits:
- Enables selective test execution by label
- Documents the intent of test groups
- Facilitates test maintenance and debugging
- Allows cross-referencing related tests
Sources: test/evidence/slt_lang_aggfunc.test:187-195 test/evidence/slt_lang_aggfunc.test:375-383 test/evidence/slt_lang_aggfunc.test:409-417
Expected Output Format
Single-Value Results
For single values, the expected output follows immediately after the ---- separator:
query I nosort
SELECT count(*) FROM t1
----
5
Multi-Row Results
For multiple rows, each row is on a separate line:
query I rowsort label-20
SELECT pk FROM tab0 WHERE (col0 BETWEEN 14 AND 89)
----
2
3
6
7
8
9
Multi-Column Results
Multi-column results are specified with each row on its own line, columns separated by the column count in the query type:
query II nosort
SELECT x, y FROM t1 ORDER BY x
----
0 false
1 true
2 true
NULL Values
NULL values in results may be represented differently depending on context:
-
In numeric columns: The literal
NULLor the label markerlabel-NULL -
In text columns: The string "NULL" (as actual data)
query I nosort label-NULL SELECT min(x) FROM t1 WHERE y='null'
NULL
Sources: test/evidence/slt_lang_aggfunc.test:361-369 test/index/between/10/slt_good_1.test:230-239
Practical Examples
Example 1: Testing Aggregate Functions with nosort
This example validates the avg() function behavior with floating-point results:
query R nosort
SELECT avg(x) FROM t1
----
1.250
The R type indicates a real (floating-point) result, and nosort ensures exact value matching.
Sources: test/evidence/slt_lang_aggfunc.test:173-177
Example 2: Testing BETWEEN Operator with rowsort
This example validates that BETWEEN syntax and explicit comparison are equivalent:
query I rowsort label-20
SELECT pk FROM tab0 WHERE (col0 BETWEEN 14 AND 89)
----
2
3
6
7
8
9
query I rowsort label-20
SELECT pk FROM tab0 WHERE (( col0 >= 14 AND col0 <= 89))
----
2
3
6
7
8
9
Both queries share the same label and expected results, demonstrating semantic equivalence.
Sources: test/index/between/10/slt_good_1.test:230-249
Example 3: Complex Query with Hash Comparison
query I rowsort label-10
SELECT pk FROM tab0 WHERE col0 < 72 AND ((col3 >= 83 AND ...
----
9 values hashing to 0b2f3ce47428ebec5f2931eddc864093
The complex WHERE clause generates 9 results, which are validated via hash rather than listing all values.
Sources: test/index/between/10/slt_good_1.test:181-183
Result Comparison Mode Decision Tree
Diagram: Choosing the Appropriate Comparison Mode
Sources: test/evidence/slt_lang_aggfunc.test1 test/evidence/slt_lang_aggfunc.test:28-31 test/index/between/10/slt_good_1.test:90-97
Comparison Mode Summary
| Mode | Order-Sensitive | Use Case | Example Count |
|---|---|---|---|
nosort | ✓ Yes | ORDER BY validation, deterministic queries | Any |
rowsort | ✗ No | Set operations, unordered results | ≤ threshold |
| Hash-based | ✗ No | Large result sets | > threshold |
Implementation Notes
- nosort comparison : Results are compared line-by-line in the order returned
- rowsort comparison : Both actual and expected results are sorted lexicographically before comparison
- Hash comparison : Results are counted and hashed; only count and hash value are compared
- Label markers : Do not affect comparison logic; used only for organization and filtering
Sources: test/evidence/slt_lang_aggfunc.test:28-500 test/index/between/10/slt_good_1.test:90-809
Related Directives
Result comparison modes work in conjunction with:
- Query type specifiers (I, T, R, II, etc.) - define expected column types
- Statement directives (
statement ok,statement error) - do not use comparison modes - Conditional directives (
skipif,onlyif) - control test execution but not comparison
For complete directive documentation, see Test Directives Reference.
Sources: test/evidence/slt_lang_aggfunc.test:3-16 test/index/between/10/slt_good_1.test:3-53