Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ XML_PROGRAMLISTING = YES
#---------------------------------------------------------------------------

INPUT = src/
FILE_PATTERNS = *.sql *.template
# Only the generated *.sql is documented. *.template files are build-time
# sources (they still carry $RELEASE_VERSION placeholders and duplicate their
# generated .sql), so documenting them produced a spurious second `version`
# memberdef with placeholder text.
FILE_PATTERNS = *.sql
RECURSIVE = YES
EXCLUDE_PATTERNS = *_test.sql

Expand Down
6 changes: 2 additions & 4 deletions src/v3/version.template
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ DROP FUNCTION IF EXISTS eql_v3.version();
--!
--! @note Auto-generated during build from src/v3/version.template
--!
--! @example
--! -- Check installed EQL version
--! SELECT eql_v3.version();
--! -- Returns: '3.0.0'
--! Example: `SELECT eql_v3.version()` returns the installed version string,
--! e.g. `'3.0.0'` (or `'DEV'` for development builds).
CREATE FUNCTION eql_v3.version()
RETURNS text
IMMUTABLE STRICT PARALLEL SAFE
Expand Down
49 changes: 47 additions & 2 deletions tasks/docs/doxygen-filter.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
#!/usr/bin/env bash
#MISE description="Doxygen input filter for SQL files"
set -euo pipefail

# Converts SQL-style comments (--!) to C++-style comments (//!)
sed 's/^--!/\/\/!/g' "$1"
# Doxygen always calls an INPUT_FILTER with exactly one filename. Guard it so a
# stray invocation fails fast instead of `awk` blocking on stdin and hanging the
# docs job.
if [ "$#" -ne 1 ]; then
echo "usage: $(basename "$0") <file.sql>" >&2
exit 2
fi

Comment thread
coderdan marked this conversation as resolved.
# Prepares SQL for Doxygen's C++ parser. Two transforms:
#
# 1. `--!` doc comments -> `//!` so Doxygen sees them.
# 2. Strip dollar-quoted function bodies (`$$ ... $$`), leaving just the
# declaration and its trailing clauses. Doxygen parses SQL heuristically as
# C++, and body SQL derails it: a `::type` cast reads as C++ scope
# resolution and drops the whole enclosing CREATE FUNCTION memberdef (this
# silently lost jsonb_path_query and ~hundreds of generated extractor
# overloads), while keywords/calls in bodies (`SELECT`, `RETURN NEXT`,
# `array_length(...)`) get mis-parsed as spurious functions. Bodies carry no
# documentation, so removing them is lossless for the generated reference
# and leaves Doxygen only clean `CREATE FUNCTION name(args) RETURNS ...`
# declarations to read. Only bare `$$` quoting is used in this codebase.
awk '
/^--!/ { print "//!" substr($0, 4); next }
{
out = ""
s = $0
while (length(s) > 0) {
p = index(s, "$$")
if (inbody) {
if (p == 0) { s = ""; break } # whole remainder is body: drop
s = substr(s, p + 2) # resume after the closing $$
inbody = 0
} else {
if (p == 0) { out = out s; break } # no body marker: keep as-is
out = out substr(s, 1, p - 1) # keep code before opening $$
s = substr(s, p + 2)
inbody = 1
}
}
# Regular SQL `--` comments read as C++ code to Doxygen (e.g.
# `-- per-entry overloads (...)` mints a phantom `overloads(...)` function),
# so neutralize them to C++ line comments on the (body-stripped) code.
gsub(/--/, "//", out)
print out
}
' "$1"
45 changes: 45 additions & 0 deletions tasks/docs/generate/test_xml_to_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,50 @@ def test_schema_qualified_type():

print("✓ Schema-qualified type test passed")

def _load_process_function():
"""Load process_function from the hyphenated module by path."""
import importlib.util

spec = importlib.util.spec_from_file_location(
"eql_xml_to_markdown", Path(__file__).parent / "xml-to-markdown.py"
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.process_function

def test_internal_schema_is_private():
"""Functions in the eql_v3_internal schema are flagged private via <type>.

Doxygen puts the schema in the memberdef <type> ("CREATE FUNCTION
eql_v3_internal"), not the <name>, so visibility is a schema check. Guards
against regressing to the old leading-underscore-name heuristic, which
flagged none of the internal surface (reporting everything public).
"""
from xml.etree import ElementTree as ET

process_function = _load_process_function()

def memberdef(schema, fn):
return ET.fromstring(f'''
<memberdef kind="function">
<name>{fn}</name>
<type>CREATE FUNCTION {schema}</type>
<argsstring>(val jsonb) RETURNS bytea</argsstring>
<briefdescription><para>Extract a term.</para></briefdescription>
<detaileddescription></detaileddescription>
</memberdef>
''')

internal = process_function(memberdef("eql_v3_internal", "eq_term"))
assert internal is not None, "internal function should be extracted"
assert internal["is_private"] is True, "eql_v3_internal.* must be private"

public = process_function(memberdef("eql_v3", "jsonb_path_query"))
assert public is not None, "public function should be extracted"
assert public["is_private"] is False, "eql_v3.* must be public"

print("✓ Internal-schema private detection test passed")

if __name__ == '__main__':
print("Running xml-to-markdown tests...\n")

Expand All @@ -163,6 +207,7 @@ def test_schema_qualified_type():
test_variants_no_self_reference()
test_param_name_type_swap()
test_schema_qualified_type()
test_internal_schema_is_private()

print("\n✅ All tests passed!")
sys.exit(0)
Expand Down
11 changes: 9 additions & 2 deletions tasks/docs/generate/xml-to-markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,15 @@ def process_function(memberdef):
if op_match:
func_name = op_match.group(1) # Use operator as function name

# Check if this is a private/internal function
is_private = func_name.startswith('_')
# Check if this is a private/internal function.
# Internal functions live in the `eql_v3_internal` schema. Doxygen puts the
# schema in the memberdef <type> (e.g. "CREATE FUNCTION eql_v3_internal"),
# not the <name>, so a schema check is required — the older bare
# leading-underscore convention alone flags none of them (which left the
# whole surface reported as public). Keep the underscore check too.
type_elem = memberdef.find('type')
type_text = extract_para_text(type_elem) if type_elem is not None else ''
is_private = func_name.startswith('_') or 'eql_v3_internal' in type_text
Comment thread
coderdan marked this conversation as resolved.

# Extract descriptions
brief = extract_description(memberdef.find('briefdescription'))
Expand Down
Loading