跳到主要内容

语义层与 SQL 编译:数据集、视图、转换

30 秒导读: PandasAI v3 不再把 CSV/数据库当成"一堆 DataFrame",而是先让你写一份声明式的数据集定义(schema:有哪些列、来自哪里、要做什么清洗),再由一个 schema→SQL 编译器把它翻译成真正的 SQL。神奇的一点:哪怕数据源是本地 CSV,查询也统一交给 DuckDB 执行——于是"本地文件"和"MySQL 表"在上层看起来完全一样。这一章讲清楚这份 schema 的结构、校验规则,以及它怎么被一步步编成 SQL。

本章聚焦 v3 的数据抽象层与其背后的 SQL 编译器。不涉及 Agent 主循环(那是 01-agent-loop.md),也不涉及"LLM 生成的代码怎么被当 SQL 校验执行"(那是 02-code-as-sql.md)。这里只回答一个问题:一份数据集定义,是怎么变成一条可执行 SQL 的?


1. 这是什么(零基础也能懂)

一句话定义

语义层(semantic layer)= 一份描述"数据长什么样、从哪来、要怎么加工"的声明式配置,PandasAI 把它存成一个目录:一份 schema.yaml(定义)+ 一份 data.parquet(如果是本地数据,连数据一起存)。

解决什么问题 / 给谁用

假设你有一张销售 CSV,想让 AI 反复查询它。你不想每次都重新描述"amount 是金额、要按 category 分组、空值填 0"。语义层让你一次性把这些规则写死在数据集里:

  • 列的元信息:每列叫什么、什么类型、什么含义(喂给 LLM 当上下文,也用来生成 SQL)。
  • 数据源:是本地 CSV,还是远程 Postgres 表,还是多表拼出来的视图。
  • 加工规则:分组、聚合、清洗转换(填空、改大小写、去重……)。

写好之后,不管底层是文件还是数据库,上层拿到的都是一个统一的 DataFrame 对象,查询体验一致。

它能做什么

能力说明
定义本地数据集pai.create(path, df=..., columns=...) → 落成 schema.yaml + data.parquet
定义远程 SQL 数据集source={"type":"postgres","table":...},不落数据,查询时才连库
定义视图(view)多张已有数据集按 relations join 成一张逻辑表
声明式转换transformations=[{"type":"fill_na",...}],编译成 SQL 表达式而非 pandas 代码
分组聚合group_by + 列的 expression(如 sum(amount)),编成 GROUP BY

用起来什么样

下面是把一个 DataFrame 落成语义层数据集的最小例子(摘自 pandasai/__init__.pycreate docstring):

import pandasai as pai

pai.create(
path="my-org/sales",
df=sales_df,
columns=[
{"name": "category", "type": "string"},
{"name": "amount", "type": "float",
"expression": "sum(amount)", "alias": "total_sales"}, # 聚合列
],
transformations=[
{"type": "fill_na", "params": {"column": "amount", "value": 0}},
],
group_by=["category"],
)
# → 落地 datasets/my-org/sales/{schema.yaml, data.parquet}

这一句 create 之后,my-org/sales 就成了一个可复用的数据集;之后 pai.load("my-org/sales") 拿回来的 DataFrame,底层查询已经带上了 fill_nasum(amount)group by category

一句话直觉

把语义层当"数据库里的视图定义 + dbt model":你写的是"我想要什么样的一张表",而不是"怎么一步步算出来"。真正的计算被延迟到查询时,由 SQL 引擎(DuckDB 或远程库)完成。

本节到此不碰底层。下面进入"它大概怎么转"。


2. 顶层全景(它大概怎么转)

整条链路分两半:写入侧(把定义落成文件)和读取侧(把文件编译成 SQL 并执行)。

写入侧: pai.create(df, columns, transformations, group_by)
│ 组装 + pydantic 校验

SemanticLayerSchema ──► schema.yaml (定义)
│ data.parquet (本地数据,可选)

读取侧: DatasetLoader.create_loader_from_schema(schema) ← 工厂,按 source 分派

┌────────┼─────────────┬───────────────┐
▼ ▼ ▼ ▼
Local SQL View (每个 loader 自带一个 query_builder)
Loader Loader Loader
│ │ │
▼ ▼ ▼
LocalQuery SqlQuery ViewQuery ← schema→SQL 编译器 (sqlglot)
Builder Builder Builder
│ │ │
└────────┴──────┬──────┘

build_query() → 一条 SQL 字符串

┌─────────────┴──────────────┐
▼ ▼
DuckDB 执行 远程连接器执行
(本地 CSV/parquet, (transpile 到目标方言 +
统一走 DuckDB) pandasai_sql 等)

怎么读这张图: 上半是"定义如何落地",下半是"定义如何被编译执行"。关键在中间那层——同一份 schema,按 source 类型分派给不同 loader,但它们共享同一套 SQL 编译器基类

各部件一句话职责:

部件干什么在哪个文件
SemanticLayerSchema数据集的 pydantic 模型 + 全部校验规则pandasai/data_loader/semantic_layer_schema.py:270
create()把 df/columns/transformations 组装成 schema 并落盘pandasai/__init__.py:46
DatasetLoader抽象基类 + 工厂,按 source 分派三种 loaderpandasai/data_loader/loader.py:22
LocalDatasetLoader本地 CSV/parquet,用 DuckDB 执行pandasai/data_loader/local_loader.py:17
SQLDatasetLoader远程 SQL 库,transpile + 安全校验后交连接器pandasai/data_loader/sql_loader.py:19
ViewDatasetLoader多表视图,先加载依赖数据集再 joinpandasai/data_loader/view_loader.py:21
VirtualDataFrame惰性 DataFrame,不预加载,查询时才打 SQLpandasai/dataframe/virtual_dataframe.py:14
BaseQueryBuilderschema→SQL 编译器基类(sqlglot)pandasai/query_builders/base_query_builder.py:12
ViewQueryBuilder把多表按 relations 拼成带 join 的子查询pandasai/query_builders/view_query_builder.py:16
SQLTransformationManager把语义层 transformation 翻成 SQL 表达式pandasai/query_builders/sql_transformation_manager.py:9
SQLParser方言转换(transpile)、表名替换pandasai/query_builders/sql_parser.py:10

3. 核心原理(逐个机制,由浅入深)

3.1 SemanticLayerSchema:pydantic 模型与它的校验

要解决的小问题: 数据集定义可以来自用户手写的 YAML,也可以来自 create() 的参数。无论哪来,进入系统前必须保证结构合法——不能有拼错的类型、矛盾的配置、非法的分组。

思路: 用 pydantic 把每个概念建成一个模型,把"什么算合法"写成校验器(validator)。非法输入在构造对象那一刻就抛错,不会带病往下走。

模型结构一览:

SemanticLayerSchema ← 顶层:一个数据集
├── source: Source ← 数据从哪来(本地 path / 远程 connection+table)
├── view: bool ← 或者:这是多表拼出来的视图
├── columns: [Column] ← 每列:name / type / expression / alias
├── relations: [Relation] ← 视图专用:表间 join 关系 (from → to)
├── transformations: [...] ← 清洗规则,每条 = type + params
├── group_by: [str] ← 分组列
└── order_by / limit / ...

Column 携带的关键字段(semantic_layer_schema.py:46):nametype(必须属于 VALID_COLUMN_TYPES)、expression(聚合表达式,构造时用 sqlglot parse_one 试解析,解析不了就报"Invalid SQL expression")、alias

顶层的 validate_schema 在对象构造完成后(mode="after")依次跑三道校验(semantic_layer_schema.py:303):

校验一:group_by 规则(聚合与分组必须自洽)

这是数据库里的老规矩:用了 GROUP BY,那么每个 SELECT 列要么在 group_by 里,要么是聚合_validate_group_by_columns(semantic_layer_schema.py:316)把它写死:

# 摘自 _validate_group_by_columns
if col.expression and col.name in group_by_set:
raise ValueError(...) # 聚合列不能又出现在 group_by
if not col.expression and col.name not in group_by_set:
raise ValueError(...) # 非聚合列必须出现在 group_by

一句话:expression = 聚合列,不许进 group_by;没 expression = 普通列,一旦指定了 group_by 就必须进 group_by。这保证编译出的 SQL 不会因为分组不完整而报错。

校验二:view 列必须是 dataset.column 格式

_validate_columns_relations(semantic_layer_schema.py:333)用一个正则区分两种世界:

column_re_check = r"^[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+$" # 形如 orders.amount
  • 如果 self.view 为真:所有列名、所有 relation 的 from/to 都必须是 表.列 两段式——因为视图要跨多张表,列名必须带表前缀才不歧义。此外若列里涉及多张表(len(tables)>1),每张表都必须被 relations 覆盖到,否则报 No relations provided for ...
  • 如果不是 view(普通表):列名反过来不许带点号,必须是纯 列名 单段式。

校验三:source 与 view 互斥

同在 _validate_columns_relations(semantic_layer_schema.py:348):

if self.source and self.view:
raise ValueError("Only one of 'source' or 'view' can be defined.")
if not self.source and not self.view:
raise ValueError("Either 'source' or 'view' must be defined.")

一个数据集要么有实际数据源(source),要么是纯逻辑视图(view),二者必居其一且只能其一

此外 Source 自己也有校验(semantic_layer_schema.py:228):本地类型(csv/parquet)必须给 path;远程类型必须给 connection + table;类型不在白名单直接报错。

关键细节: 校验分两层——字段级 @field_validator(如列类型白名单)在赋值时触发;对象级 @model_validator(mode="after") 在整个对象组装完后触发(因为 group_by 规则要同时看 columns 和 group_by,必须等两者都在)。

3.2 create():把 df + 定义落成 schema.yaml + data.parquet

要解决的小问题: 用户给的是零散参数(df、columns 列表、transformations 字典);要变成上面那个严格的 schema 对象并存盘。

流程(__init__.py:46 create):

1. 校验 path 格式 → org_name / dataset_name (:141)
2. 若目录已存在同名 schema → 报 "already exists" (:150)
3. 把字典参数解析成模型:
Transformation(**t) / Column(**c) / Relation(**r) (:161-164, 180)
4. 按三种情况组装 schema:
├─ 有 df → 复用 df.schema,覆盖 name/columns/...
│ 再 model_validate,并 df.to_parquet 落数据 (:166-178)
├─ view → 用 relations 构造 view=True 的 schema (:179-188)
└─ source → 用 source 构造远程 schema (:189-196)
5. schema.to_yaml() 写入 schema.yaml (:200)
6. 立刻建 loader 并 load(),返回可用的 DataFrame (:204-206)

有 df 时,数据被 df.to_parquet(...) 真实写盘(__init__.py:178);之后所有查询都读这个 parquet,而不是原始 CSV。这就是为什么本地数据集目录里躺着一个 data.parquet

关键细节: 落盘用的 name 是下划线格式(transform_dash_to_underscore),但建 loader 前又 sanitize_sql_table_name(__init__.py:204)——因为 name 最终要当 SQL 表名/别名用,得先洗成 SQL 安全的标识符。

3.3 DatasetLoader 工厂:按 source 类型分派三种 loader

要解决的小问题: 同样是"加载一个数据集",本地文件、远程库、视图三者的执行方式完全不同,但调用方不该关心。

思路: 一个工厂方法 create_loader_from_schema(loader.py:39)看 schema 就决定实例化哪个子类:

# 摘自 create_loader_from_schema
if schema.source and schema.source.type in LOCAL_SOURCE_TYPES:
loader = LocalDatasetLoader(schema, dataset_path) # csv / parquet
elif schema.view:
loader = ViewDatasetLoader(schema, dataset_path) # 多表视图
else:
loader = SQLDatasetLoader(schema, dataset_path) # 远程 SQL 库
loader.query_builder.validate_query_builder() # 建完立刻试编译一次

分派逻辑一目了然:

条件Loaderquery_builder执行引擎
source.type ∈ {csv, parquet}LocalDatasetLoaderLocalQueryBuilderDuckDB
schema.view == TrueViewDatasetLoaderViewQueryBuilderDuckDB 或远程
其余(有 source)SQLDatasetLoaderSqlQueryBuilder远程连接器

关键细节: 工厂最后一行 validate_query_builder()(loader.py:59)会立刻跑一遍 build_query() 并用 sqlglot 解析——相当于"编译期自检",schema 有问题在加载时就暴露,而不是等真查询时才崩。另有一个 create_loader_from_path(loader.py:63)从磁盘读 schema.yaml 再走同一条工厂。

3.4 VirtualDataFrame:惰性执行

要解决的小问题: 远程库有几亿行,不可能一 load 就全拉进内存。

思路: 远程/视图 loader 的 load() 返回的不是真数据,而是一个 VirtualDataFrame(virtual_dataframe.py:14)——它持有 loader,但不预取任何数据。真正要数据时才打 SQL:

# 摘自 VirtualDataFrame
def head(self):
if self._head is None:
self._head = self._loader.load_head() # 要预览了,才发 LIMIT 查询
return self._head

@property
def rows_count(self) -> int:
return self._loader.get_row_count() # 要行数了,才发 COUNT(*)

def execute_sql_query(self, query: str):
return self._loader.execute_query(query) # Agent 生成的 SQL 从这里执行

对比之下,LocalDatasetLoader.load()(local_loader.py:35)是即时的——它当场执行 build_query() 把 parquet 读成真 DataFrame。差别在于:本地数据量可控,远程/视图则用虚拟表延迟到用时再算。


4. 深入实现:schema 怎么被编成 SQL

4.1 BaseQueryBuilder.build_query:用 sqlglot 拼 SQL

编译器的核心在 build_query(base_query_builder.py:25)。它不拼字符串,而是用 sqlglot 的表达式 API 一块块搭:

# 摘自 build_query
query = select(*self._get_columns()).from_(self._get_table_expression())
if self.schema.group_by:
query = query.group_by(*[normalize_identifiers(col) for col in self.schema.group_by])
if self._check_distinct(): # 有 remove_duplicates 转换 → DISTINCT
query = query.distinct()
if self.schema.order_by:
query = query.order_by(*self.schema.order_by)
if self.schema.limit:
query = query.limit(self.schema.limit)
return query.transform(quote_identifiers).sql(pretty=True)

三个"零件"函数由子类各自实现,这正是多态的用武之地:

零件Base(基类)默认子类覆盖
_get_columns()遍历列,处理 expression/alias/transformationView 版还要 alias 化列名
_get_table_expression()用 schema.name 当表名Local 版→read_csv/read_parquet(...);SQL 版→真实表名;View 版→带 join 的子查询

结尾 query.transform(quote_identifiers):统一给所有标识符加引号,防止列名撞上 SQL 关键字。

4.2 _get_columns:expression / alias / transformation 三件事

_get_columns(base_query_builder.py:64)是列级编译的关键,它对每一列决定"这列在 SELECT 里长什么样":

# 摘自 _get_columns
for col in self.schema.columns:
if col.expression:
column_expr = col.expression # 聚合列:直接用表达式 sum(amount)
else:
column_expr = normalize_identifiers(col.name).sql() # 普通列:规范化列名

if self.schema.transformations: # 该列若有清洗转换
column_expr = self.transformation_manager.apply_column_transformations(
column_expr, col.name, self.schema.transformations) # 包一层 SQL 函数
col.alias = col.alias or normalize_identifiers(col.name).sql()

if col.alias:
column_expr = f"{column_expr} AS {col.alias}" # 加别名
columns.append(column_expr)

三步流水:expression 决定基础表达式 → transformation 在外面套一层 SQL 函数 → alias 收尾。举例:列 amountfill_na(0) 转换,最终生成 COALESCE(amount, 0) AS amount

4.3 SQLTransformationManager:转换 → SQL 表达式

思路: 语义层里的每个转换是一个 {type, params},manager 把 type 映射到一个生成 SQL 片段的方法(sql_transformation_manager.py:9)。命名约定是"_<type>"——apply_transformationsgetattr 找到对应方法逐个套上去(sql_transformation_manager.py:36)。

几个代表(注意:它们层层嵌套,前一个的输出是后一个的输入):

type生成的 SQL源码
fill_naCOALESCE(expr, value):57
to_lowercaseLOWER(expr):83
round_numbersROUND(expr, decimals):91
map_valuesCASE WHEN expr = 'k' THEN 'v' ... END:67
clipLEAST(GREATEST(expr, lo), hi):168
remove_duplicatesDISTINCT expr(也触发 SELECT DISTINCT):213

apply_column_transformations(sql_transformation_manager.py:272)先用 get_column_transformationsparams.column 筛出只作用于这一列的转换,再依次套用。所以清洗被编译进 SELECT 表达式,而不是事后跑 pandas——转换发生在 SQL 层,这正是 v3 的核心转变。

4.4 ViewQueryBuilder:多表按 relations 拼成 join 子查询

视图是最复杂的一支。ViewQueryBuilder._get_table_expression(view_query_builder.py:100)负责把"几张独立数据集"拼成一张逻辑表。步骤:

1. 找出第一张表(relations[0].from_ 的表名),
从它的 loader 取子查询做 FROM 基底 (:103-116)
2. 按 relation.to 的目标表分组 join 条件,
同一目标表的多个条件用 AND 合并 (:118-127)
3. 逐个目标表:取其 loader 的子查询,JOIN 上去 (:129-136)
→ 得到一个 "把所有源表拼齐" 的内层子查询
4. 在这个子查询外面再 SELECT 真正要的列,
带上 group_by,包成带 alias 的外层子查询 (:141-148)

关键在 _get_sub_query_from_loader(view_query_builder.py:96):每张依赖表不是直接引用表名,而是把该表自己的 build_query() 当作子查询嵌进来——于是每张源表自身的列定义、转换、别名都先各自编译好,视图只管在上面 join。列名用 sanitize_view_column_name 处理点号(orders.amount → 安全别名),避免 SQL 里的歧义。

依赖数据集从哪来?ViewDatasetLoader 在构造时按 relations 里出现的表名,逐个 create_loader_from_path 把它们加载进来(view_loader.py:50),并检查它们的 source 彼此兼容(全本地,或同一个远程连接)才允许拼视图(view_loader.py:64,底层是 is_compatible_source)。

4.5 方言转换与连接器映射

编译出来的 SQL 是 sqlglot 的"通用"方言,执行前要 transpile 到目标库的方言。SQLParser.transpile_sql_dialect(sql_parser.py:60):

# 摘自 transpile_sql_dialect
placeholder = "___PLACEHOLDER___"
query = query.replace("%s", placeholder) # 先把参数占位符藏起来,别被解析器动
query = parse_one(query)
result = query.sql(dialect=to_dialect, pretty=True) # 输出目标方言
if to_dialect == "duckdb":
return result.replace(placeholder, "?") # DuckDB 用 ? 占位
return result.replace(placeholder, "%s") # 其余用 %s

远程执行时(sql_loader.py:39 execute_query):先 transpile 到 source.type 方言 → 过 is_sql_query_safe 安全校验(见 02-code-as-sql.md)→ 按 source 类型查连接器表 SUPPORTED_SOURCE_CONNECTORS 找到对应包并动态 import(sql_loader.py:65):

source 类型连接器包
mysql / postgres / cockroachdb / sqlserver / oraclepandasai_sql
bigquerypandasai_bigquery
snowflakepandasai_snowflake
databrickspandasai_databricks
yahoo_financepandasai_yfinance

映射表定义在 constants.py:23,取回的模块里调 load_from_<type> 函数执行(sql_loader.py:70)。

4.6 为什么本地 CSV 也走 SQL:统一用 DuckDB

这是 v3 最值得记住的设计:本地文件不用 pandas 直接读,而是让 DuckDB 用 SQL 读

LocalQueryBuilder._get_table_expression(local_query_builder.py:13)不返回表名,而是返回 DuckDB 的文件读取函数:

# 摘自 LocalQueryBuilder._get_table_expression
if source_type == "parquet":
return f"read_parquet('{abspath}')" # FROM read_parquet('/…/data.parquet')
elif source_type == "csv":
return f"read_csv('{abspath}')"

于是本地数据集的 build_query() 生成的 SQL 形如 SELECT ... FROM read_parquet('...'),LocalDatasetLoader.execute_query(local_loader.py:53)把它交给 DuckDBConnectionManager(duck_db_connection_manager.py:8)执行——后者内部还会再 transpile 到 duckdb 方言(duck_db_connection_manager.py:31)。

这样做的回报:

  • 一套编译器打天下——group_by、transformation、view join 的所有逻辑写一遍,本地和远程共用,不用为 pandas 和 SQL 各写一份。
  • 本地也能被 Agent 生成的 SQL 直接查——Agent 产出的分析是 SQL,不管数据在文件还是库里都能跑。
  • 视图能混合本地表——因为本地表也是"可被 SQL join 的东西",只要都在同一个 DuckDB 会话里。

代价是本地读取多绕了一层 SQL 引擎,但换来了整条管线的统一,值得。


5. 巧妙之处(可借鉴的技术)

  • schema 即真理,校验前置。 所有"数据集合不合法"的判断集中在 pydantic 模型里(semantic_layer_schema.py:303),非法输入在构造那一刻就崩,下游不用再防。
  • 编译期自检。 工厂建完 loader 立刻 validate_query_builder()(loader.py:59)跑一次 build_query + sqlglot 解析,把"schema 能不能编成合法 SQL"提前到加载时暴露。
  • 用 sqlglot 表达式 API 而非字符串拼接。 select().from_().group_by()(base_query_builder.py:25)天然防注入、可 transform、可跨方言输出,比手拼 SQL 干净得多。
  • 视图 = 子查询套子查询。 每张源表先各自 build_query 成子查询,视图只在上面 join(view_query_builder.py:96),让"表自身的列/转换/别名"和"表间关系"彻底解耦。
  • 占位符藏猫猫。 transpile 前先把 %s 换成不会被 SQL 解析器碰的哨兵串,转完再换回目标方言的占位符(sql_parser.py:63),避免参数占位符在方言转换中被误伤。
  • 统一 DuckDB 执行本地数据(local_query_builder.py:22)——用一套 SQL 管线抹平"文件 vs 数据库"的差异,是整章的点睛。

6. 边界与局限

  • 转换语义未必 1:1。 SQLTransformationManager 生成的 SQL 用的是较通用的函数名(如 DATE_FORMATCONVERT_TZREGEXP sql_transformation_manager.py:98/127/199),不同库对这些函数的支持和行为可能有差异,transpile 也不保证每个都能完美翻译。
  • 视图要求源兼容。 拼视图的所有源必须全本地或同一远程连接(view_loader.py:64);跨不同数据库拼视图不支持,会直接报错。
  • group_by 是强约束。 一旦指定 group_by,每个非聚合列都必须列进去(semantic_layer_schema.py:327),否则构造即失败——比某些 SQL 方言的宽松行为更严。
  • 本地数据一旦 create 就固化成 parquet。 createdf.to_parquet(__init__.py:178),之后查的是这份 parquet 快照,不是活的原始文件;源文件变了不会自动反映。
  • 连接器是可选依赖。 远程源需要额外装 pandasai_sql[...] 等包,没装则在执行时才报 ImportError(sql_loader.py:55),不是加载时。

7. 横向对比

同属 ai-agent-reference 的 data-agents:PandasAI 的取舍是**"把数据抽象成声明式 schema,再统一编译成 SQL 交给 DuckDB/远程库执行"**——计算下推到 SQL 引擎,Agent 只负责生成 SQL(见 01-agent-loop.md)和被当 SQL 校验安全执行(见 02-code-as-sql.md)。这与"让 LLM 直接写 pandas 代码在内存里跑"的路线相反:后者灵活但难沙箱、难跨源;PandasAI 用 SQL 换来了统一的执行面和更强的可校验性。

全景与阅读地图见本组 index.md


8. 代码地图(导航索引)

主题文件路径符号名
数据集 pydantic 模型 + 全部校验pandasai/data_loader/semantic_layer_schema.pySemanticLayerSchemavalidate_schema
group_by 规则校验pandasai/data_loader/semantic_layer_schema.py_validate_group_by_columns
view 列格式 / source-view 互斥pandasai/data_loader/semantic_layer_schema.py_validate_columns_relations
列 / 源 / 关系 / 转换模型pandasai/data_loader/semantic_layer_schema.pyColumnSourceRelationTransformation
落盘为 schema.yaml + data.parquetpandasai/__init__.pycreate
Loader 工厂(按 source 分派)pandasai/data_loader/loader.pyDatasetLoader.create_loader_from_schema
本地 loader(DuckDB 执行)pandasai/data_loader/local_loader.pyLocalDatasetLoader
远程 SQL loaderpandasai/data_loader/sql_loader.pySQLDatasetLoader.execute_query
视图 loader(加载依赖 + 兼容校验)pandasai/data_loader/view_loader.pyViewDatasetLoader
惰性 DataFramepandasai/dataframe/virtual_dataframe.pyVirtualDataFrame
schema→SQL 编译器基类pandasai/query_builders/base_query_builder.pyBaseQueryBuilder.build_query_get_columns
本地文件表达式(read_csv/parquet)pandasai/query_builders/local_query_builder.pyLocalQueryBuilder._get_table_expression
远程表名表达式pandasai/query_builders/sql_query_builder.pySqlQueryBuilder
视图 join 编译pandasai/query_builders/view_query_builder.pyViewQueryBuilder._get_table_expression
转换 → SQL 表达式pandasai/query_builders/sql_transformation_manager.pySQLTransformationManagerapply_column_transformations
方言 transpile / 表名替换pandasai/query_builders/sql_parser.pySQLParser.transpile_sql_dialect
DuckDB 会话管理pandasai/data_loader/duck_db_connection_manager.pyDuckDBConnectionManager
连接器映射 / 类型白名单pandasai/constants.pySUPPORTED_SOURCE_CONNECTORSLOCAL_SOURCE_TYPES