英文:
How to serialize runtime Postgres queries in OCaml using Caqti?
问题
以下是您要翻译的内容:
我有一个使用情景,用户以字符串形式向我提出查询,执行查询,然后将结果以 JSON 形式返回。
我正在使用 Caqti 库,它允许我执行类型安全的查询,问题在于查询的类型取决于查询本身,在我的情况下在编译时是未知的。
我通过 HTTP 请求接收查询,并需要以 JSON 格式响应结果,为简化起见,假设所有列都是字符串是可以接受的。在这种情况下,一个具有类型 string -> string list list
的函数足够,它接收查询作为 string
并将结果作为 string list list
返回。
-- 更新 --
我创建了一个示例,由于这需要使用 Dune,我将其放在了这个 Gist 中:https://gist.github.com/dhilst/f1ac36163ba08bbeb8cf6fa806b6aaa2
要运行它,您可以使用 dune init project foo
初始化一个项目,然后将来自 Gist 的 .ml
文件粘贴到 Dune 创建的 bin/main.ml
中,并用 Gist 中的 dune
文件替换 bin/dune
文件。
与问题相关的部分如下:
我想从用户那里得到一个 SQL 查询的字符串
let query: string = Sys.argv.(1) in (* 这是用户的查询 *)
然后我想运行这个查询,并接收一个 string string list
,其中包含值。可以通过以下函数完成
val do_query (query : string) : (string list list, string) Lwt_result.t
其中 string list
是一行数据,string list list
是一组行。我使用 string list
是因为我不知道它会有多少列。
现在的问题是,在 Caqti 中,我必须表示我的查询的类型。在示例中,我有一个包含这些列的表 (id int, content text)
,我可以这样表示查询 SELECT id, content FROM todos
Caqti_type.(unit ->* (tup2 int string))
"SELECT id, content FROM todos"
我的问题是,我不知道用户会输入什么查询,所以我不知道要在上面的函数中放什么,也就是我的问题所在。
-- 更新 --
我使用了 Richard 的想法,但我没有创建一个函数,而是使用了 to_jsonb
。解决方案是像这样定义 user_query
let user_query q : (unit, string, [ `Many | `One | `Zero ]) Caqti_request.t =
let cte = Format.sprintf {|
WITH q AS (%s)
SELECT to_jsonb(q.*)::text FROM q
|} q in
Caqti_type.(unit ->* string) cte
现在它将每一行输出为 JSON。这甚至更好,因为我可以逐行发送结果,这样就不需要将所有内容都存储在内存中。
我在此处更新了 Gist:https://gist.github.com/dhilst/d0f878cc19b43c78136d69e035f5370d
英文:
I have a use case where I receive a query from the user as a string. execute the query, and return the output as json.
I'm using the Caqti library, which let me do type safe queries, the problem is that the type of the query depends on the query, which is unknown at compilation time in my case.
I'm receiving the query in a HTTP request and need to respond with the results in JSON, for simplicity, assuming that all columns are strings is admissible. In such case a function with the type string -> string list list
which receives the query as string
and returns the results as string list list
is enough.
-- update --
I created an example, since this requires dune I put it in this gist: https://gist.github.com/dhilst/f1ac36163ba08bbeb8cf6fa806b6aaa2
To run you can initialize a project with dune init project foo
, then paste the .ml
file from gist in bin/main.ml
created by dune, and replace the bin/dune
file with the dune
file from the gist.
The relevant parts for the question are:
I want to get a string from the user which is a SQL query
let query: string = Sys.argv.(1) in (* this is the user query *)
Then I want to run this query and receive a string string list
with the values. Which may me done by this function
val do_query (query : string) : (string list list, string) Lwt_result.t
where string list
is a single row, and string list list
is a list of rows. I'm using string list
because I don't know how many columns it would have.
Now the problem is that in Caqti I have to express the type of my query. In the example I have a table todos
with these columns (id int, content text)
, I can express the type of the query SELECT id, content FROM todos
like this
Caqti_type.(unit ->* (tup2 int string))
"SELECT id, content FROM todos"
My problem is that I don't know the query user will input so I can't know what to put in Caqti_type.( ??? ) user_query
In the example I left a function like this
let user_query q =
Caqti_type.(unit ->* unit (* ??? what I put here? *)) q
q
is a string with the user query (which I don't know), I don't know what to put in place of the second unit
in the above function, that is precisely my problem.
-- update --
I use Richard's idea but instead of creating a function I used to_jsonb
. The solution was to define user_query
like this
let user_query q : (unit, string, [ `Many | `One | `Zero ]) Caqti_request.t =
let cte = Format.sprintf {|
WITH q AS (%s)
SELECT to_jsonb(q.*)::text FROM q
|} q in
Caqti_type.(unit ->* string) cte
Now it outputs each row as JSON. This is even better because I can send the results row by row so that I don't need to put it all in memory.
I update the gist here: https://gist.github.com/dhilst/d0f878cc19b43c78136d69e035f5370d
答案1
得分: 3
好的,以下是翻译好的部分:
"OK, so anything involving directly executing SQL from a user makes me very nervous, but presuming you know what you are doing..."
"让PostgreSQL与您一起工作,而不是与静态类型抗争。如果您想要返回JSON,只需让PostgreSQL返回JSON。"
"Write a plpgsql function to accept your SQL string, wrap it in the aggregation and use EXECUTE to run the composed query."
- "编写一个plpgsql函数来接受您的SQL字符串,将其包装在聚合中,并使用EXECUTE运行组合查询。"
请注意,上述链接没有被翻译,因为它们是URL链接,无需翻译。
英文:
OK, so anything involving directly executing SQL from a user makes me very nervous, but presuming you know what you are doing...
Get PostgreSQL to work with you on this, rather than fighting the static typing. If you want JSON returned then just get PostgreSQL to return JSON.
WITH source_rows AS (
SELECT i, CURRENT_DATE + i AS dt FROM generate_series(0, 2) i
)
SELECT jsonb_agg(row_to_json(source_rows.*))
FROM source_rows;
┌────────────────────────────────────────────────────────────────────────────────────────────┐
│ jsonb_agg │
├────────────────────────────────────────────────────────────────────────────────────────────┤
│ [{"i": 0, "dt": "2023-07-03"}, {"i": 1, "dt": "2023-07-04"}, {"i": 2, "dt": "2023-07-05"}] │
└────────────────────────────────────────────────────────────────────────────────────────────┘
(1 row)
Write a plpgsql function to accept your SQL string, wrap it in the aggregation and use EXECUTE
to run the composed query.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论