修改postgres_proto源代码将原始sql传入自定义处理器的query_tables函数实现查询duckdb真实数据

根据postgres_proto文档描述的select语句处理过程,

子类化 PostgresRequestHandler
PostgresRequestHandler 覆盖了 PostgresServerFlowMixin 中的 execute_query() 方法以提供 SQL 语句处理。
它与 QueryInformationSchemaMixin 和 QueryPostgresBuiltinsMixin 集成,以确保正确处理这些表。

当调用 execute_query() 时,SQL 查询使用 postgres_proto.sql.parser.parse_sql() 进行解析,该解析器提供 stmt_type 和 stmt_info。
有关更多详细信息,请参见 parse_sql()。

解析后,将调用与语句类型关联的处理程序。

SELECT 语句已被处理。当收到 SELECT 语句时,将调用 query_tables()。您必须覆盖此函数。
query_tables() 必须返回一个元组,其中第一项是字典列表(行),第二项是名称列表(列名)。

截获传入解析器的原始sql文本,然后根据报错信息逐步在调用链源代码中增加这个文本作为返回值或参数,一直传递到自定义处理器的query_tables函数,成功绕过解析器解析后的内容,把原始sql传给了duckdb,从而实现任意语句查询,对于duckdb报出的sql错误,也能由消息返回

对调用链源代码修改
parse.py

def parse_sql(sql, stmt_type_delimiters=None):
    stmt_type, parts = split_sql(sql, stmt_type_delimiters)
    stmt_types = {
        'SELECT': transform_select_stmt
    }
    return stmt_type, stmt_types[stmt_type](parts) if stmt_type in stmt_types else parts,sql #将sql附在解析结果后面

报错

  File "/par/pypo/postgres_proto/socket_handler/__init__.py", line 30, in execute_query
    stmt_type, stmt_info = self.parse_sql(query)
    ^^^^^^^^^^^^^^^^^^^^
ValueError: too many values to unpack (expected 2)


对上述__init__.py修改


    def execute_query(self, query):
        stmt_type, stmt_info,sql = self.parse_sql(query)  #变成读取3个返回值,其实后来看不需要,这里的query就是原始sql

        if self.is_postgres_builtins_query(stmt_type, stmt_info):
            return self.handle_postgres_builtins_query(stmt_type, stmt_info)

        if self.is_information_schema_query(stmt_type, stmt_info):
            return self.handle_information_schema_query(stmt_type, stmt_info)

        handler = self.get_stmt_handler(stmt_type)
        if not handler:
            if stmt_type in self.ignore_missing_statement_types:
                return stmt_type, None, None
            raise PostgresError('statement type not supported')
        rows, cols = handler(stmt_info,sql) #给处理器传入2个参数,实际处理器还有self参数,所以下面报错说3个
        return stmt_type, rows, cols

报错

  File "/par/pypo/postgres_proto/socket_handler/__init__.py", line 43, in execute_query
    rows, cols = handler(stmt_info,sql)
                 ^^^^^^^^^^^^^^^^^^^^^^
TypeError: PostgresRequestHandler.handle_select() takes 2 positional arguments but 3 were given

继续修改__init__.py

    @stmt_handler('SELECT')
    def handle_select(self, stmt_info, query):
        if '*' in [c.name for c in stmt_info.columns] and (len(stmt_info.columns) > 1 or stmt_info.columns[0].alias):
            raise PostgresError('select * cannot be aliased or used with other columns')

        data, cols = self.query_tables(stmt_info, query)
        return format_select_results(data, cols, stmt_info)

自定义处理器程序

from postgres_proto.socket_handler import PostgresRequestHandler
from postgres_proto.flow import PostgresError, catch_all_as_postgres_error_context
import duckdb

class DuckDBRequestHandler(PostgresRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        #self.connection = duckdb.connect('demo.ddb')
    
    def query_tables(self, stmt_info,sql): #改成3个参数
        with catch_all_as_postgres_error_context():
            query = str(stmt_info)
            connection = duckdb.connect('/par/demo.ddb')
            print("sql========",sql)
            result = connection.execute(sql) #原始sql传给了duckdb
            columns = [desc[0] for desc in result.description]
            rows_data = result.fetchall()
            
            # 转换为JSON格式的字典列表
            rows = []
            for row in rows_data:
                row_dict = {}
                for i, value in enumerate(row):
                    row_dict[columns[i]] = value
                rows.append(row_dict)
            print(rows, columns)
            return rows, columns
    
    def list_tables(self):
        with catch_all_as_postgres_error_context():
            result = self.connection.execute("SHOW TABLES")
            return [row[0] for row in result.fetchall()]
    
    def describe_table(self, table_name):
        with catch_all_as_postgres_error_context():
            result = self.connection.execute(f"DESCRIBE {table_name}")
            return [row[0] for row in result.fetchall()]

if __name__ == '__main__':
    from postgres_proto.server import start_server, cli_arg_parser
    start_server(DuckDBRequestHandler, **vars(cli_arg_parser.parse_args()))

测试消息文本文件

'Q'	"SELECT * FROM foods where category='meat'"
'Y'
'Q'	"SELECT category,fats_g FROM foods where sugars_g<1"
'Y'

服务端

/par/pypo/examples# PYTHONPATH=.. python3 duck2.py
Serving on 127.0.0.1:55432
sql======== SELECT * FROM foods where category='meat'
[{'category': 'meat', 'calories': 100, 'fats_g': 5.0, 'sugars_g': 0}, {'category': 'meat', 'calories': 120, 'fats_g': 10.0, 'sugars_g': 1}, {'category': 'meat', 'calories': 110, 'fats_g': 7.0, 'sugars_g': 0}, {'category': 'meat', 'calories': 110, 'fats_g': 7.0, 'sugars_g': 0}, {'category': 'meat', 'calories': 100, 'fats_g': 7.0, 'sugars_g': 0}] ['category', 'calories', 'fats_g', 'sugars_g']

sql======== SELECT category,fats_g FROM foods where sugars_g<1
[{'category': 'seafood', 'fats_g': 5.0}, {'category': 'meat', 'fats_g': 5.0}, {'category': 'seafood', 'fats_g': 5.0}, {'category': 'fruit', 'fats_g': 4.5}, {'category': 'meat', 'fats_g': 7.0}, {'category': 'seafood', 'fats_g': 5.0}, {'category': 'seafood', 'fats_g': 10.0}, {'category': 'meat', 'fats_g': 7.0}, {'category': 'seafood', 'fats_g': 1.5}, {'category': 'meat', 'fats_g': 7.0}] ['category', 'fats_g']

客户端

src/pgproto -h 127.0.0.1 -p 55432 -f /par/pgpo2.txt
FE=> Query (query="SELECT * FROM foods where category='meat'")
<= BE RowDescription
字段数量: 4
字段 1: category
字段 2: calories
字段 3: fats_g
字段 4: sugars_g
<= BE DataRow
meat|100|5.0|0
<= BE DataRow
meat|120|10.0|1
<= BE DataRow
meat|110|7.0|0
<= BE DataRow
meat|110|7.0|0
<= BE DataRow
meat|100|7.0|0
<= BE CommandComplete(SELECT)
<= BE ReadyForQuery(I)
FE=> Query (query="SELECT category,fats_g FROM foods where sugars_g<1")
<= BE RowDescription
字段数量: 2
字段 1: category
字段 2: fats_g
<= BE DataRow
seafood|5.0
<= BE DataRow
meat|5.0
<= BE DataRow
seafood|5.0
<= BE DataRow
fruit|4.5
<= BE DataRow
meat|7.0
<= BE DataRow
seafood|5.0
<= BE DataRow
seafood|10.0
<= BE DataRow
meat|7.0
<= BE DataRow
seafood|1.5
<= BE DataRow
meat|7.0
<= BE CommandComplete(SELECT)
<= BE ReadyForQuery(I)

如果表不存在,则报错,这个错误来自duckdb

src/pgproto -h 127.0.0.1 -p 55432 -f /par/pgpo2.txt
FE=> Query (query="SELECT * FROM foods where category='meat'")
<= BE ErrorResponse(S ERROR C 0 M Catalog Error: Table with name foods does not exist!
Did you mean "pg_constraint"?

LINE 1: SELECT * FROM foods where category='meat'
                      ^ )
<= BE ReadyForQuery(I)

如果是在python交互环境执行,效果如下

>>> sql="SELECT * FROM food where category='meat'"
>>> result = connection.execute(sql)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
duckdb.duckdb.CatalogException: Catalog Error: Table with name food does not exist!
Did you mean "foods"?

LINE 1: SELECT * FROM food where category='meat'
                      ^
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值