Skip to content

Sitting Duck

Query source code with SQL. CSS selectors, pattern matching, scope-aware call graphs — across 27 languages.

-- Functions that call execute() but have no error handling
SELECT name, file_path
FROM ast_select('src/**/*.py',
    '.func:has(.call#execute):not(:has(try_statement))');

Sitting Duck is a DuckDB extension that parses source code into ASTs using tree-sitter grammars, then exposes them as SQL tables you can query, join, and aggregate.

What You Can Ask

Structural search with CSS selectors

Query AST nodes the way you'd query a DOM — type selectors, combinators, pseudo-classes, :has(), :not():

-- Async functions that don't await anything
SELECT name FROM ast_select('src/**/*.js',
    'function_declaration:async:not(:has(await_expression))');

-- Class methods that follow another method (sibling combinator)
SELECT name FROM ast_select('src/**/*.py',
    'class_definition function_definition ~ function_definition');

-- Who calls this function? (pseudo-element navigation)
SELECT name FROM ast_select('src/**/*.py', '.func#validate::callers');

~80 semantic aliases (.func, .class, .call, .loop, .if, .namespace, ...) work identically across all 27 languages. Full selector reference →

Pattern matching by example

Find code structures using real syntax with named wildcards:

-- Capture function names and their return values
SELECT captures['F'].name AS func, captures['X'].peek AS returns
FROM ast_match('src/*.py',
    'def __F__(__):
        return __X__', 'python');

-- Find dangerous function calls and capture what's being passed
SELECT captures['X'].peek AS dangerous_input, file_path
FROM ast_match('src/**/*.py', 'subprocess.call(__X__)', 'python');

Pattern matching guide →

Scope-aware analysis

Every node carries a scope struct with precomputed shortcuts — no range joins needed:

-- What function contains each call?
SELECT name, scope.function AS enclosing_fn
FROM read_ast('src/**/*.py')
WHERE semantic_type = 'COMPUTATION_CALL';

-- All class methods (inside a class, not a nested function)
SELECT name, scope.class
FROM read_ast('src/**/*.py')
WHERE semantic_type = 'DEFINITION_FUNCTION'
  AND scope.class IS NOT NULL;

Call graphs

-- Who calls validate?
SELECT caller, call_line FROM ast_callers('src/**/*.py')
WHERE callee = 'validate';

-- What does main call?
SELECT callee, callee_line FROM ast_callees('src/**/*.py')
WHERE caller = 'main';

Cross-language semantic types

The same query works on Python, JavaScript, Rust, Go, C++, and 22 more languages:

SELECT language, COUNT(*) AS functions
FROM read_ast(['src/**/*.py', 'src/**/*.js', 'src/**/*.go'])
WHERE semantic_type = 'DEFINITION_FUNCTION'
GROUP BY language;

Quick Start

INSTALL sitting_duck FROM community;
LOAD sitting_duck;

-- Parse and explore
SELECT name, type, start_line
FROM read_ast('src/**/*.py')
WHERE semantic_type = 'DEFINITION_FUNCTION'
ORDER BY start_line;

-- Parse once, query many (interactive workflow)
CREATE TABLE ast AS SELECT * FROM read_ast('src/**/*.py');
SELECT * FROM ast_select_from('ast', '.func:has(return_statement)');
SELECT * FROM ast_select_from('ast', '.class:named');

27 Languages

Category Languages
Web JavaScript, TypeScript, HTML, CSS
Systems C, C++, Go, Rust, Zig
Scripting Python, Ruby, PHP, Lua, R, Bash
Enterprise Java, C#, Kotlin, Swift, Dart
Data SQL, DuckDB, GraphQL, JSON
Config HCL (Terraform), TOML
Docs Markdown

Documentation

  • Tutorials — installation, first query, CSS selectors, pattern matching
  • How-to Guides — practical recipes for common tasks
  • Reference — functions, output schema, selector syntax, semantic types
  • Explanation — how parsing, scope, and the selector engine work
  • AI Agent Guide — using Sitting Duck with Claude Code, Cursor, and other AI tools

Why "Sitting Duck"?

  • Sitting — a nod to Tree-sitter, our parsing engine
  • Duck — everything quacks like data in DuckDB
  • Your codebase becomes a sitting duck for SQL-based analysis