Skip to content

Repository files navigation

RTGL

PyPI Python arXiv License: MIT

RTGL (Relational Task Generation Language) is a Python framework for writing compact, expressive predictive queries over relational data, with a focus on Relational Deep Learning, and inspired by the proprietary PQL from KumoAI.

Defining a prediction task over relational databases usually means hand-writing SQL or pandas pipelines for entity selection, temporal joins, and label aggregation: code that is verbose, easy to get subtly wrong, and rarely reusable across tasks. RTGL replaces that boilerplate: declare what to predict and for whom, and RTGL compiles the query to SQL, optionally executes it, and returns the result.

🧠 Features

  • 🔀 Two converters: SConverter for static prediction queries, TConverter for temporal queries evaluated at a set of prediction timestamps.
  • 🔍 Automatic validation: built-in syntactic and semantic checks reject malformed queries and schema mismatches before any SQL is executed.
  • 🔗 Multi-hop relationships: resolves the shortest foreign-key path between two tables automatically, not just direct relations.
  • 🧵 Common Path Expressions (CPEs): name an explicit join path and reference it like a regular table, including cases where several equally short paths make automatic resolution ambiguous.
  • 💉 SQL Injections: drop a raw SQL query in as a virtual table, declare its keys, and use it anywhere a table is expected.
  • ⚙️ Dual output mode: execute=False returns the generated SQL; execute=True runs it on DuckDB and returns a Table object.

📦 Installation

pip install rtgl

🚀 Quickstart

1. Describe Your Database

RTGL operates over a Database of Table objects. Wrap each pandas DataFrame with its primary key, its foreign keys, and an optional time column.

A database can be supplied either as a RelBench Database object or through RTGL's own simplified equivalent, shown below.

import pandas as pd
from rtgl.base import Database, Table

users = pd.DataFrame({
    "user_id": [1, 2, 3],
    "registration_date": pd.to_datetime(["2024-01-01", "2025-07-23", "2026-08-08"]),
    # ... other columns
})
orders = pd.DataFrame({
    "user_id": [1, 1, 1, 3],
    "order_date": pd.to_datetime(["2026-07-05", "2026-07-20", "2026-08-10", "2026-08-15"]),
    # ... other columns
})
# ... other dataframes

db = Database(table_dict={
    "users": Table(
        df=users,
        pkey_col="user_id",
        time_col="registration_date",
    ),
    "orders": Table(
        df=orders,
        fkey_col_to_pkey_table={"user_id": "users"},
        time_col="order_date",
    ),
    # ... other tables
})

2. Static Query with SConverter

A static query produces exactly one label per entity, independent of time.

from rtgl.converter import SConverter

converter = SConverter(db)

# how many orders has each user placed, over all time?
rtgl_query = """
    PREDICT COUNT(orders.*)
    FOR EACH users.*;
"""

# returns the generated SQL query
sql_query = converter.convert(rtgl_query, execute=False)

# returns a Table object with (fk, label) columns
table = converter.convert(rtgl_query, execute=True)

print(table.df)
#    fk  label
# 0   1      3
# 1   2      0
# 2   3      1

# db can be replaced later without rebuilding the converter
new_db = ...
converter.set_db(new_db)

3. Temporal Query with TConverter

A temporal query is evaluated at a set of prediction timestamps, and every aggregation is computed over a time window relative to each one.

import pandas as pd
from rtgl.converter import TConverter

timestamps = pd.Series(pd.to_datetime(["2026-07-01", "2026-08-09"]))
converter = TConverter(db, timestamps)

# how much will each user place orders in the 30 days following each timestamp?
rtgl_query = """
    PREDICT COUNT(orders.*, 0, 30, DAYS)
    FOR EACH users.*;
"""

# returns the generated SQL query
sql_query = converter.convert(rtgl_query, execute=False) 

# returns a Table object with (fk, timestamp, label) columns
table = converter.convert(rtgl_query, execute=True) 

print(table.df)
#    fk   timestamp  label
# 0   1  2026-07-01      2  
# 1   2  2026-07-01      0
# 2   1  2026-08-09      1
# 3   2  2026-08-09      0
# 4   3  2026-08-09      1

# db and timestamps can be replaced later without rebuilding the converter
new_db = ...
converter.set_db(new_db)
new_timestamps = ...
converter.set_timestamps(new_timestamps)

Note: User 3 has no row at 2026-07-01: their registration_date (2026-08-08) falls after that timestamp, so RTGL excludes them automatically. By 2026-08-09 they exist, and a row appears. See RTGL Fundamentals for why this behaviour matters.

📚 Guides & Examples

Three guides cover the language in depth, each paired with a runnable notebook:

Guide Notebook Covers
RTGL Fundamentals 01-standard-tasks.ipynb Query anatomy, conditions, aggregations, automatic multi-hop, temporal windows
Common Path Expressions 02-common-path-expressions.ipynb Explicit join paths and ambiguity resolution
SQL Injections 03-sql-injections.ipynb Raw SQL as a virtual table

The rtgl-tasks repository collects full prediction tasks built with RTGL on real datasets.

🏗️ Architecture

Architecture

🔧 Development

Install uv

macOS and Linux:

wget -qO- https://astral.sh/uv/install.sh | sh

Windows:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Install Dependencies

uv sync --all-extras

Regenerate Parser Files

After modifying the lexer or parser grammar files (*.g4), regenerate the ANTLR outputs from the repository root:

./regenerate_parser.sh

Run Tests

pytest

Run Linter

ruff check .

📄 License

RTGL is released under the MIT License.

About

RTGL is a Python framework for task generation in Relational Deep Learning. It provides a Relational Task Generation Language to simplify task generation when working with complex static/temporal relational data.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages