Search Shortcut cmd + k | ctrl + k
sitting_duck

Parse and analyze source code ASTs from 28 programming languages with tree-sitter grammars, pattern matching, and structural search

Maintainer(s): teaguesterling

Compatibility Matrix

This table covers all DuckDB releases that have not reached end-of-life, including LTS releases, and shows the 2 most recent patch releases of each. Older patch releases are not listed, but the extension may be available for them as well.

Platform 1.4.4 1.4.5 1.5.5 1.5.6 2.0.0
Linux AMD64
Linux ARM64
macOS AMD64
macOS ARM64
Windows AMD64
Windows ARM64
WebAssembly

Installing and Loading

INSTALL sitting_duck FROM community;
LOAD sitting_duck;

Example

-- Parse Python code and find function definitions
SELECT name, start_line, peek
FROM parse_ast('
def hello():
    return "hello world"
def greet(name):
    print(f"Hello, {name}!")
    return name
', 'python')
WHERE is_function_definition(semantic_type);

-- Pattern matching: find eval() calls and capture arguments
SELECT captures['X'].peek as dangerous_input, file_path, start_line
FROM ast_match('code', 'eval(__X__)', 'python');

-- Pattern matching with variadic wildcards
SELECT captures['F'].name as func_name, captures['Y'].peek as return_value
FROM ast_match('code',
    'def __F__(__):
    %__<BODY*>__%
    return __Y__', 'python');

-- Security audit: find dangerous function calls
SELECT * FROM ast_security_audit('code')
WHERE risk_level = 'high';

-- Function complexity metrics
SELECT name, cyclomatic, max_depth, lines
FROM ast_function_metrics('code')
WHERE cyclomatic > 10;

-- Cross-language analysis with semantic types
SELECT language, COUNT(*) as functions
FROM read_ast(['src/**/*.py', 'src/**/*.js', 'src/**/*.go'])
WHERE is_function_definition(semantic_type)
GROUP BY language;

About sitting_duck

Sitting Duck is a DuckDB extension for parsing source code into Abstract Syntax Trees (ASTs) using tree-sitter grammars. It provides a powerful SQL interface to analyze, search, and understand code structure across 25+ programming languages.

Documentation: https://sitting-duck.readthedocs.io/

Core Features

Pattern Matching: Find code structures using pattern-by-example matching with wildcards:

-- Recursive wildcards for deep matching
SELECT * FROM ast_match('code', 'class __C__: %__<**>__% def __M__(self): %__<BODY*>__%', 'python');

-- Optional and negation wildcards
SELECT * FROM ast_match('code', 'def __F__(__<?ARGS>__): %__<BODY*>__%', 'python');

See: https://sitting-duck.readthedocs.io/en/latest/guide/pattern-matching/

CSS Selector Queries (v1.6.0, enhanced in v1.7.0+): Query AST nodes using CSS selector syntax — bootstrapped with sitting duck's own CSS grammar:

SELECT name FROM ast_select('src/*.py', '.func:has(.call#execute):not(:has(try_statement))');

-- v1.7.0: :match (current-node) vs :contains (subtree) structural patterns
SELECT name FROM ast_select('src/*.py', '.func:contains("db.execute()")');
SELECT name FROM ast_select('src/*.py', 'call:match("db.execute()")');

-- v1.7.2: parse once, query many — orders of magnitude faster for interactive use
CREATE TABLE my_ast AS SELECT * FROM read_ast('src/**/*.py');
SELECT * FROM ast_select_from('my_ast', '.class:named');
SELECT * FROM ast_select_from('my_ast', '.func:has(return_statement)');

Supports type selectors, #name, .semantic (~80 aliases), combinators, :has(), :not(), :match() / :contains() structural patterns, scope/call-graph pseudo-classes, and pseudo-element navigation. Bare type matching: if matches if + if_statement + if_clause.

v1.10.0 adds runtime-loadable tree-sitter grammars (register_language(), off by default behind sitting_duck_enable_runtime_grammars), ast_to_blocks (render code structure as duck_blocks documents), and ast_patch/ast_replace (AST-anchored source patching and selector-driven rewriting in SQL).

v1.9.0 hardens the selector engine: no more silent-empty results (selectors the engine can't honor now raise a clear error instead of returning 0 rows), #name on call nodes binds uniformly across all 27 grammars, and NULL columns no longer over- or under-match.

Parse-time filtering (v1.8.0): max_depth := and prune := shrink the AST at parse time, with automatic tree healing:

SELECT * FROM read_ast('file.py', max_depth := 2);
SELECT * FROM read_ast('src/**/*.py', prune := ['syntax', 'comments', 'punctuation']);

Prune policies: syntax, comments, literals, imports, types, punctuation, unnamed, leaves, internal.

Call-graph macros (v1.8.0):

  • ast_get_calls(source) - calls with caller attribution and type classification
  • ast_call_graph(source) - aggregated caller→callee graph with call counts
  • ast_find_references(source, name) - scope-chain-aware symbol reference resolution

Table Functions:

  • read_ast(file_pattern, language := NULL) - Parse source files into AST rows (parallel)
  • parse_ast(content, language) - Parse source code strings (table function)
  • parse_ast_list(content, language) - Parse source code strings (scalar, returns LIST; v1.7.0)
  • ast_match(source, pattern, lang) - Pattern matching for code search
  • ast_select(source, css_selector) - CSS selector queries
  • ast_select_from(table_name, selector) - CSS selectors on pre-parsed tables (v1.7.2)
  • ast_type_map(language) - Node type discovery across languages

v1.7.0 extraction suffix: Any extraction parameter (context, source, structure, peek) can take a +schema suffix that keeps the full column set in the output schema as NULLs without computing them:

-- Keep peek column in schema but skip its computation
SELECT name, peek FROM read_ast('file.py', peek := 'none+schema');

Enables SQL macros to declare stable schemas regardless of which columns they populate.

Relational Predicates (v1.5.0):

  • ast_has(source, parent_type, child_type) - Check containment relationships
  • ast_inside(source, child_type, parent_type, parent_name) - Find nodes inside specific parents
  • ast_precedes(source, before_type, after_type) - Ordering predicates
  • ast_follows(source, after_type, before_type) - Ordering predicates
  • ast_not_has(source, parent_type, child_type) - Negated containment

Analysis Macros (file-path based):

  • ast_definitions(source) - All named definitions with categories
  • ast_function_metrics(source) - Cyclomatic complexity, nesting depth, line counts
  • ast_security_audit(source) - Detect dangerous function call patterns
  • ast_dead_code(source) - Find potentially unused functions/classes
  • ast_nesting_analysis(source) - Identify deeply nested code
  • ast_definition_parent(table) - Resolve nearest definition ancestor per node

Tree Navigation:

  • ast_descendants(table, node_id) - Get subtree (O(1) using descendant_count)
  • ast_ancestors(table, node_id) - Path from node to root
  • ast_children(table, node_id) - Immediate children
  • ast_function_scope(table, node_id) - Function body excluding nested functions

Semantic Predicates: Cross-language filtering with normalized types:

  • is_function_definition(st), is_class_definition(st), is_variable_definition(st)
  • is_function_call(st), is_literal(st), is_conditional(st), is_loop(st)

Supported Languages (27)

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
Mobile Dart
Data SQL, JSON, TOML, GraphQL, HCL
Documentation Markdown

AST Schema

Each parsed node includes:

  • type, name - Node type and extracted identifier
  • semantic_type - Normalized type for cross-language queries
  • file_path, language - Source location
  • start_line, end_line, depth - Position and nesting
  • descendant_count - For O(1) subtree queries
  • peek - Configurable source preview
  • qualified_name - Scope-based path unique within a file (C[User] F[__init__] format; v1.7.0)
  • signature_type, parameters, modifiers, annotations - Native extraction (v1.8.0: modifiers[] populated across Python, JS, TS, Rust, Kotlin, Swift, Dart, C#; IS_EXPORTED flag for file-level visibility)
  • scope - STRUCT<current, function, class, module, stack> (v1.7.4); replaces the v1.7.2 scope_id / scope_stack columns. scope.function lets you answer "what function is this in?" as a single column read.

Full schema: https://sitting-duck.readthedocs.io/en/latest/api/output-schema/

Use Cases

  • Security auditing - Find dangerous patterns (eval, exec, SQL injection)
  • Code quality - Complexity metrics, dead code detection, nesting analysis
  • Refactoring - Pattern-based code search across large codebases
  • Documentation - Extract function signatures and structure
  • AI workflows - Structured code understanding for LLM tools

GitHub: https://github.com/teaguesterling/sitting_duck

Added Functions

function_name function_type description comment examples
ast_ancestors table_macro NULL NULL  
ast_call_arguments table_macro NULL NULL  
ast_call_graph table_macro NULL NULL  
ast_callees table_macro NULL NULL  
ast_callers table_macro NULL NULL  
ast_capture macro NULL NULL  
ast_children table_macro NULL NULL  
ast_class_members table_macro NULL NULL  
ast_containing_line table_macro NULL NULL  
ast_dead_code table_macro NULL NULL  
ast_definition_parent table_macro NULL NULL  
ast_definitions table_macro NULL NULL  
ast_descendants table_macro NULL NULL  
ast_dispatch_predicate macro NULL NULL  
ast_exports table_macro NULL NULL  
ast_find_references table_macro NULL NULL  
ast_follows table_macro NULL NULL  
ast_function_metrics table_macro NULL NULL  
ast_function_scope table_macro NULL NULL  
ast_functions_containing table_macro NULL NULL  
ast_get_calls table_macro NULL NULL  
ast_get_source macro NULL NULL  
ast_get_source_line macro NULL NULL  
ast_get_source_numbered macro NULL NULL  
ast_has table_macro NULL NULL  
ast_imports table_macro NULL NULL  
ast_in_range table_macro NULL NULL  
ast_inside table_macro NULL NULL  
ast_match table_macro NULL NULL  
ast_nesting_analysis table_macro NULL NULL  
ast_node_edit macro NULL NULL  
ast_not_has table_macro NULL NULL  
ast_patch table_macro NULL NULL  
ast_pattern table_macro NULL NULL  
ast_pattern_list macro NULL NULL  
ast_peek_contains_any scalar Case-sensitive search checking if peek string contains any pattern in the list (alias of string_contains_any). NULL [ast_peek_contains_any(peek, ['import', 'require'])]
ast_precedes table_macro NULL NULL  
ast_qualified_name_as_string macro NULL NULL  
ast_replace table_macro NULL NULL  
ast_resolve table_macro NULL NULL  
ast_resolve_entity table_macro NULL NULL  
ast_security_audit table_macro NULL NULL  
ast_select table_macro NULL NULL  
ast_select_from table_macro NULL NULL  
ast_select_list table_macro NULL NULL  
ast_select_rules table_macro NULL NULL  
ast_selector_for table_macro NULL NULL  
ast_semantic_aliases table Return every semantic class usable in an ast_select selector (e.g. .fn, .call, .def), what it resolves to, and how broadly it matches. NULL [SELECT selector, resolves_to FROM ast_semantic_aliases() ORDER BY selector]
ast_siblings table_macro NULL NULL  
ast_source_bytes scalar Read a file's pristine bytes as a BLOB, one row at a time. NULL when the path does not exist (including the '' pseudo-path of parse_ast()). This is the per-row substrate byte-exact unparse needs, which read_blob() cannot provide because table functions take only constant arguments. NULL [SELECT ast_source_bytes('src/main.py'), SELECT fp, ast_source_bytes(fp) AS cblob FROM (SELECT DISTINCT file_path AS fp FROM nodes)]
ast_source_of table_macro NULL NULL  
ast_supported_languages table Return list of supported programming and markup languages and their registered grammar metadata. NULL [SELECT * FROM ast_supported_languages()]
ast_to_blocks table_macro NULL NULL  
ast_to_blocks_from table_macro NULL NULL  
ast_to_blocks_list table_macro NULL NULL  
ast_type_map table Return mapping of AST node types to semantic types and extraction strategies. NULL [SELECT * FROM ast_type_map(), SELECT * FROM ast_type_map('python')]
ast_unparse table_macro NULL NULL  
ast_unparse_code table_macro NULL NULL  
ast_unparse_custom table_macro NULL NULL  
ast_unparse_exact table_macro NULL NULL  
ast_unparse_exact_code table_macro NULL NULL  
ast_unparse_exact_from table_macro NULL NULL  
ast_unparse_exact_splice table_macro NULL NULL  
ast_unparse_from table_macro NULL NULL  
ast_unparse_rules table Return active unparse layout and spacing rules. NULL [SELECT * FROM ast_unparse_rules(), SELECT * FROM ast_unparse_rules('python'), SELECT * FROM ast_unparse_rules('python', 'black'), SELECT * FROM ast_unparse_rules('go', 'gofmt')]
binds_name scalar Returns true if node binds a name in lexical scope (definition or declaration). NULL [binds_name(flags)]
clean_pattern macro NULL NULL  
detect_language scalar Detect tree-sitter language name from file extension, or return NULL if unknown. NULL [detect_language('src/main.rs')]
get_kind scalar Get the primary kind name (FUNCTION, CLASS, VARIABLE, etc.) for a semantic type. NULL [get_kind(semantic_type)]
get_searchable_types scalar Get list of all searchable semantic type codes. NULL [get_searchable_types()]
get_super_kind scalar Get the super-kind category name (DEFINITION, EXECUTION, STRUCTURE, LITERAL, etc.) for a semantic type. NULL [get_super_kind(semantic_type)]
has_body scalar Returns true if node has an embodied implementation body. NULL [has_body(flags)]
is_annotation macro NULL NULL  
is_arithmetic macro NULL NULL  
is_assignment macro NULL NULL  
is_block macro NULL NULL  
is_boolean_literal macro NULL NULL  
is_call scalar Check if a semantic type represents a function/method call expression. NULL [is_call(semantic_type)]
is_class_definition macro NULL NULL  
is_comment macro NULL NULL  
is_comparison macro NULL NULL  
is_conditional macro NULL NULL  
is_constituent scalar Returns true if node is an internal constituent part of a compound construct. NULL [is_constituent(flags)]
is_construct scalar Returns true if node is a meaningful semantic construct (inverse of is_syntax_only). NULL [is_construct(flags)]
is_control_flow scalar Check if a semantic type represents a control flow construct (if, loop, switch, return, etc.). NULL [is_control_flow(semantic_type)]
is_declaration_only scalar Returns true if node is a declaration without an accompanying implementation body. NULL [is_declaration_only(flags)]
is_definition scalar Check if a semantic type represents a definition (function, class, variable, etc.). NULL [is_definition(semantic_type)]
is_directive macro NULL NULL  
is_embodied scalar Returns true if node is an embodied definition (has implementation body). NULL [is_embodied(flags)]
is_export macro NULL NULL  
is_exported scalar Returns true if node is exported from its containing module/package. NULL [is_exported(flags)]
is_foreign macro NULL NULL  
is_function_call macro NULL NULL  
is_function_definition macro NULL NULL  
is_identifier scalar Check if a semantic type represents an identifier (variable name, function name, etc.). NULL [is_identifier(semantic_type)]
is_import macro NULL NULL  
is_jump macro NULL NULL  
is_kind scalar Check if a semantic type code belongs to the specified kind. NULL [is_kind(semantic_type, 'FUNCTION')]
is_list macro NULL NULL  
is_literal macro NULL NULL  
is_logical macro NULL NULL  
is_loop macro NULL NULL  
is_member_access macro NULL NULL  
is_module_definition macro NULL NULL  
is_name_declaration scalar Returns true if node represents a name declaration. NULL [is_name_declaration(flags)]
is_name_definition scalar Returns true if node represents a name definition. NULL [is_name_definition(flags)]
is_name_reference scalar Returns true if node represents a name reference. NULL [is_name_reference(flags)]
is_number_literal macro NULL NULL  
is_parser_specific scalar Check if a semantic type is parser-specific/unclassified in the universal taxonomy. NULL [is_parser_specific(semantic_type)]
is_pattern_wildcard macro NULL NULL  
is_punctuation scalar Check if a semantic type represents punctuation or syntax delimiter. NULL [is_punctuation(semantic_type)]
is_scope scalar Returns true if node establishes a lexical scope boundary (function, class, block, module). NULL [is_scope(flags)]
is_semantic_type scalar Check if a semantic type bitmask matches a taxonomy pattern string. NULL [is_semantic_type(semantic_type, 'DEFINITION_%')]
is_string_literal macro NULL NULL  
is_syntax_only scalar Returns true if node is a pure syntax token (keyword, punctuation) with no semantic payload. NULL [is_syntax_only(flags)]
is_type_composite macro NULL NULL  
is_type_definition macro NULL NULL  
is_type_generic macro NULL NULL  
is_type_primitive macro NULL NULL  
is_type_reference macro NULL NULL  
is_variable_definition macro NULL NULL  
kind_code scalar Get the numeric code for a taxonomy kind name. NULL [kind_code('FUNCTION')]
name_role scalar Get the numeric NameRole code (0=NONE, 1=DECLARATION, 2=DEFINITION, 3=REFERENCE) from flags. NULL [name_role(flags)]
parse_ast table Parse source code string into an AST table representation. NULL [SELECT * FROM parse_ast('def add(a, b): return a + b', 'python')]
parse_ast_flat table Parse source code string into a flat AST table representation (alias of parse_ast). NULL [SELECT * FROM parse_ast_flat('def add(a, b): return a + b', 'python')]
parse_ast_hierarchical table Parse source code string into a hierarchical AST table representation. NULL [SELECT * FROM parse_ast_hierarchical('def add(a, b): return a + b', 'python')]
parse_ast_list scalar Parse source code string into a list of AST node structs (scalar function for column-valued inputs). NULL [parse_ast_list('x = 1', 'python')]
parse_ast_list_table table_macro NULL NULL  
parse_html_wildcard macro NULL NULL  
pattern_has_recursive macro NULL NULL  
pattern_has_variadic macro NULL NULL  
read_ast table Parse source code from file(s) into an AST table representation. NULL [SELECT * FROM read_ast('src/main.cpp'), SELECT * FROM read_ast('src/main.cpp', 'cpp')]
read_ast table NULL NULL  
read_ast_flat table Parse source code from file(s) into flat AST table representation (alias of read_ast). NULL [SELECT * FROM read_ast_flat('src/main.cpp'), SELECT * FROM read_ast_flat('src/main.cpp', 'cpp')]
read_ast_flat table NULL NULL  
read_ast_hierarchical table Parse source code from file(s) into legacy hierarchical AST table representation. NULL [SELECT * FROM read_ast_hierarchical('src/main.cpp'), SELECT * FROM read_ast_hierarchical('src/main.cpp', 'cpp')]
read_ast_hierarchical table NULL NULL  
read_ast_hierarchical_new table Parse source code from file(s) into hierarchical AST table representation. NULL [SELECT * FROM read_ast_hierarchical_new('src/main.cpp'), SELECT * FROM read_ast_hierarchical_new('src/main.cpp', 'cpp')]
read_ast_hierarchical_new table NULL NULL  
register_language table Dynamically register a tree-sitter language grammar from a shared library. NULL [SELECT * FROM register_language('mylang', '/path/to/libtree-sitter-mylang.so')]
semantic_type_base macro NULL NULL  
semantic_type_code scalar Convert a semantic type name string to its numeric bitmask code. NULL [semantic_type_code('DEFINITION_FUNCTION')]
semantic_type_to_string scalar Convert a numeric semantic type bitmask to its human-readable name string. NULL [semantic_type_to_string(1::UTINYINT)]
sitting_duck_enable_dynamic_predicates pragma NULL NULL  
string_contains_any scalar Case-sensitive search checking if string contains any pattern in the list. NULL [string_contains_any('hello world', ['world', 'foo'])]
string_contains_any_i scalar Case-insensitive search checking if string contains any pattern in the list. NULL [string_contains_any_i('Hello World', ['world', 'foo'])]
wildcard_capture_name macro NULL NULL  

Overloaded Functions

This extension does not add any function overloads.

Added Types

type_name type_size logical_type type_category internal
SEMANTIC_TYPE 1 UTINYINT NUMERIC true

Added Settings

name description input_type scope aliases typed_value
sitting_duck_enable_runtime_grammars Allow register_language() to load native tree-sitter grammar libraries, which executes arbitrary native code in-process. Disabled by default. BOOLEAN GLOBAL [] false
temp_directory Set the directory to which temporary files are written. VARCHAR GLOBAL [] post.db.tmp