mirror of
https://github.com/tstack/lnav.git
synced 2024-09-11 13:05:51 +03:00
[blog] add a post about PRQL
This commit is contained in:
parent
dee87e5436
commit
c075da3eaa
51
docs/_posts/2024-03-29-prql-support.md
Normal file
51
docs/_posts/2024-03-29-prql-support.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
layout: post
|
||||
title: Support for the PRQL in the database query prompt
|
||||
excerpt: >-
|
||||
PRQL is a database query language that is pipeline-oriented
|
||||
and easier to use interactively
|
||||
---
|
||||
|
||||
The v0.12.1 release of lnav includes support for
|
||||
[PRQL](https://prql-lang.org). PRQL is a database query language
|
||||
that has a pipeline-oriented syntax. The main advantage of PRQL,
|
||||
in the context of lnav, is that it is easier to work with
|
||||
interactively compared to SQL. For example, lnav can provide
|
||||
previews of different stages of the pipeline and provide more
|
||||
accurate tab-completions for the columns in the result set. I'm
|
||||
hoping that the ease-of-use will make doing log analysis in lnav
|
||||
much easier.
|
||||
|
||||
You can execute a PRQL query using the existing database prompt
|
||||
(press `;`). A query is interpreted as PRQL if it starts with
|
||||
the [`from`](https://prql-lang.org/book/reference/data/from.html)
|
||||
keyword. After `from`, the database table should be provided.
|
||||
The table for the focused log message will be suggested by default.
|
||||
You can accept the suggestion by pressing TAB. To add a new stage
|
||||
to the pipeline, enter a pipe symbol (`|`), followed by a
|
||||
[PRQL transform](https://prql-lang.org/book/reference/stdlib/transforms/index.html)
|
||||
and its arguments. In addition to the standard set of transforms,
|
||||
lnav provides some convenience transforms in the `stats` and `utils`
|
||||
namespaces. For example, `stats.count_by` can be passed one or more
|
||||
column names to group by and count, with the result sorted by most
|
||||
to least.
|
||||
|
||||
As you enter a query, lnav will update various panels on the display
|
||||
to show help, preview data, and errors. The following is a
|
||||
screenshot of lnav viewing a web access log with a query in progress:
|
||||
|
||||
![Screenshot of PRQL in action](/assets/images/lnav-prql-preview.png)
|
||||
|
||||
The top half is the usual log message view. Below that is the online
|
||||
help panel showing the documentation for the `stats.count_by` PRQL
|
||||
function. lnav will show the help for what is currently under the
|
||||
cursor. The next panel shows the preview data for the pipeline stage
|
||||
that precedes the stage where the cursor is. In this case, the
|
||||
results of `from access_log`, which is the contents of the access
|
||||
log table. The second preview window shows the result of the
|
||||
pipeline stage where the cursor is located.
|
||||
|
||||
There is still a lot of work to be done on the integration and PRQL
|
||||
itself, but I'm very hopeful this will work out well in the long
|
||||
term. Many thanks to the PRQL team for starting the project and
|
||||
keeping it going, it's not easy competing with SQL.
|
BIN
docs/assets/images/lnav-prql-preview.png
Normal file
BIN
docs/assets/images/lnav-prql-preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 774 KiB |
@ -528,6 +528,9 @@ plain_text_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir)
|
||||
if (neighbors_res->cnr_next) {
|
||||
return this->line_for_offset(
|
||||
neighbors_res->cnr_next.value()->hn_start);
|
||||
} else if (!md.m_sections_root->hn_children.empty()) {
|
||||
return this->line_for_offset(
|
||||
md.m_sections_root->hn_children[0]->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
|
||||
PRQL_FILES = \
|
||||
$(srcdir)/%reldir%/stats.prql \
|
||||
$(srcdir)/%reldir%/utils.prql \
|
||||
$()
|
||||
|
@ -3,3 +3,17 @@ let count_by = func column rel <relation> -> <relation> (
|
||||
group {column} (aggregate {total = count this})
|
||||
sort {-total}
|
||||
)
|
||||
|
||||
let average_of = func column rel <relation> -> <relation> (
|
||||
rel
|
||||
aggregate {value = average column}
|
||||
)
|
||||
|
||||
let sum_of = func column rel <relation> -> <relation> (
|
||||
(rel | aggregate {total = sum column})
|
||||
)
|
||||
|
||||
let by = func column values rel <relation> -> <relation> (
|
||||
rel
|
||||
group {column} (aggregate values)
|
||||
)
|
||||
|
5
src/prql/utils.prql
Normal file
5
src/prql/utils.prql
Normal file
@ -0,0 +1,5 @@
|
||||
let distinct = func column rel <relation> -> <relation> (
|
||||
rel
|
||||
select {column}
|
||||
group {column} (take 1)
|
||||
)
|
@ -583,7 +583,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
|
||||
continue;
|
||||
}
|
||||
curr_stage_prql.insert(riter->sa_range.lr_start,
|
||||
"| take 1000 ");
|
||||
"| take 10000 ");
|
||||
}
|
||||
curr_stage_prql.rtrim();
|
||||
curr_stage_prql.append(" | take 5");
|
||||
@ -608,7 +608,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
|
||||
continue;
|
||||
}
|
||||
prev_stage_prql.insert(riter->sa_range.lr_start,
|
||||
"| take 1000 ");
|
||||
"| take 10000 ");
|
||||
}
|
||||
prev_stage_prql.append(" | take 5");
|
||||
|
||||
|
@ -572,7 +572,7 @@ static readline_context::command_t sql_commands[] = {
|
||||
.with_grouping("(", ")"))
|
||||
.with_example({
|
||||
"To group by log_level and count the rows in each partition",
|
||||
"from db.lnav_example_log | group { log_level } (aggregate { "
|
||||
"from lnav_example_log | group { log_level } (aggregate { "
|
||||
"count this })",
|
||||
help_example::language::prql,
|
||||
}),
|
||||
@ -623,6 +623,21 @@ static readline_context::command_t sql_commands[] = {
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"stats.average_of",
|
||||
prql_cmd_sort,
|
||||
help_text("stats.average_of", "Compute the average of col")
|
||||
.prql_function()
|
||||
.with_parameter(help_text{"col", "The column to average"})
|
||||
.with_example({
|
||||
"To get the average of a",
|
||||
"from [{a=1}, {a=1}, {a=2}] | stats.average_of a",
|
||||
help_example::language::prql,
|
||||
}),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"stats.count_by",
|
||||
prql_cmd_sort,
|
||||
@ -642,6 +657,38 @@ static readline_context::command_t sql_commands[] = {
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"stats.sum_of",
|
||||
prql_cmd_sort,
|
||||
help_text("stats.sum_of", "Compute the sum of col")
|
||||
.prql_function()
|
||||
.with_parameter(help_text{"col", "The column to sum"})
|
||||
.with_example({
|
||||
"To get the sum of a",
|
||||
"from [{a=1}, {a=1}, {a=2}] | stats.sum_of a",
|
||||
help_example::language::prql,
|
||||
}),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"stats.by",
|
||||
prql_cmd_sort,
|
||||
help_text("stats.by", "A shorthand for grouping and aggregating")
|
||||
.prql_function()
|
||||
.with_parameter(help_text{"col", "The column to sum"})
|
||||
.with_parameter(help_text{"values", "The aggregations to perform"})
|
||||
.with_example({
|
||||
"To partition by a and get the sum of b",
|
||||
"from [{a=1, b=1}, {a=1, b=1}, {a=2, b=1}] | stats.by a "
|
||||
"{sum b}",
|
||||
help_example::language::prql,
|
||||
}),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"sort",
|
||||
prql_cmd_sort,
|
||||
@ -681,6 +728,22 @@ static readline_context::command_t sql_commands[] = {
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"utils.distinct",
|
||||
prql_cmd_sort,
|
||||
help_text("utils.distinct",
|
||||
"A shorthand for getting distinct values of col")
|
||||
.prql_function()
|
||||
.with_parameter(help_text{"col", "The column to sum"})
|
||||
.with_example({
|
||||
"To get the distinct values of a",
|
||||
"from [{a=1}, {a=1}, {a=2}] | utils.distinct a",
|
||||
help_example::language::prql,
|
||||
}),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
};
|
||||
|
||||
static readline_context::command_map_t sql_cmd_map;
|
||||
|
@ -1145,6 +1145,9 @@ annotate_sql_statement(attr_line_t& al)
|
||||
std::vector<const help_text*>
|
||||
find_sql_help_for_line(const attr_line_t& al, size_t x)
|
||||
{
|
||||
static const auto* sql_cmd_map
|
||||
= injector::get<readline_context::command_map_t*, sql_cmd_map_tag>();
|
||||
|
||||
std::vector<const help_text*> retval;
|
||||
const auto& sa = al.get_attrs();
|
||||
std::string name;
|
||||
@ -1152,10 +1155,6 @@ find_sql_help_for_line(const attr_line_t& al, size_t x)
|
||||
x = al.nearest_text(x);
|
||||
|
||||
{
|
||||
const auto* sql_cmd_map
|
||||
= injector::get<readline_context::command_map_t*,
|
||||
sql_cmd_map_tag>();
|
||||
|
||||
auto sa_opt = get_string_attr(al.get_attrs(), &SQL_COMMAND_ATTR);
|
||||
if (sa_opt) {
|
||||
auto cmd_name = al.get_substring((*sa_opt)->sa_range);
|
||||
@ -1182,6 +1181,11 @@ find_sql_help_for_line(const attr_line_t& al, size_t x)
|
||||
al.get_attrs(), &lnav::sql ::PRQL_FQID_ATTR, x);
|
||||
if (prql_fqid_iter != al.get_attrs().end()) {
|
||||
auto fqid = al.get_substring(prql_fqid_iter->sa_range);
|
||||
auto cmd_iter = sql_cmd_map->find(fqid);
|
||||
if (cmd_iter != sql_cmd_map->end()) {
|
||||
return {&cmd_iter->second->c_help};
|
||||
}
|
||||
|
||||
auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
|
||||
|
||||
for (auto func_iter = func_pair.first; func_iter != func_pair.second;
|
||||
|
@ -1257,6 +1257,9 @@ textfile_sub_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir)
|
||||
if (neighbors_res->cnr_next) {
|
||||
return to_vis_line(
|
||||
lf, neighbors_res->cnr_next.value()->hn_start);
|
||||
} else if (!md.m_sections_root->hn_children.empty()) {
|
||||
return to_vis_line(
|
||||
lf, md.m_sections_root->hn_children[0]->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1019,7 +1019,9 @@ execute_examples()
|
||||
}
|
||||
for (auto cmd_pair : *sql_cmd_map) {
|
||||
if (cmd_pair.second->c_help.ht_context
|
||||
!= help_context_t::HC_PRQL_TRANSFORM)
|
||||
!= help_context_t::HC_PRQL_TRANSFORM
|
||||
&& cmd_pair.second->c_help.ht_context
|
||||
!= help_context_t::HC_PRQL_FUNCTION)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user