mirror of https://github.com/tstack/lnav
[prql] initial work for integrating PRQL
parent
1113320cd4
commit
bdc9c5a28d
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
let json_each = func input -> s"SELECT * FROM json_each({input})"
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "prqlc-c"
|
||||||
|
publish = false
|
||||||
|
version = "0.11.3"
|
||||||
|
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.70.0"
|
||||||
|
|
||||||
|
# This means we can build with `--features=default`, which can make builds more generic
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# We produce both of these at the moment, but could consider refining this. ref
|
||||||
|
# https://github.com/rust-lang/cargo/issues/8607 &
|
||||||
|
# https://github.com/rust-lang/rust/issues/59302
|
||||||
|
crate_type = ["staticlib", "cdylib"]
|
||||||
|
doctest = false
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "0.2.153"
|
||||||
|
prqlc = { git = "https://github.com/PRQL/prql.git" }
|
||||||
|
serde_json = "1.0.114"
|
||||||
|
|
||||||
|
[package.metadata.release]
|
||||||
|
tag-name = "{{version}}"
|
||||||
|
tag-prefix = ""
|
@ -0,0 +1,102 @@
|
|||||||
|
# PRQL C library
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This module compiles PRQL as a library (both `.a` and `.so` are generated). This
|
||||||
|
allows embedding in languages that support FFI — for example, Golang.
|
||||||
|
|
||||||
|
## Linking
|
||||||
|
|
||||||
|
See [examples/minimal-c/Makefile](examples/minimal-c/Makefile).
|
||||||
|
|
||||||
|
Copy the `.a` and `.so` files in a convenient place and add the following
|
||||||
|
compile flags to Go (cgo):
|
||||||
|
|
||||||
|
`CGO_LDFLAGS="-L/path/to/libprqlc_c.a -lprqlc -pthread -ldl" go build`
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
For a minimal example, see
|
||||||
|
[examples/minimal-c/main.c](examples/minimal-c/main.c).
|
||||||
|
|
||||||
|
Below is an example from an actual application that is using PRQL in Go.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package prql
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int to_sql(char *prql_query, char *sql_query);
|
||||||
|
int to_json(char *prql_query, char *json_query);
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToSQL converts a PRQL query to SQL
|
||||||
|
func ToSQL(prql string) (string, error) {
|
||||||
|
// buffer length should not be less than 1K because we may get an error
|
||||||
|
// from the PRQL compiler with a very short query
|
||||||
|
cStringBufferLength := 1024
|
||||||
|
|
||||||
|
// allocate a buffer 3 times the length of the PRQL query to store the
|
||||||
|
// generated SQL query
|
||||||
|
if len(prql)*3 > cStringBufferLength {
|
||||||
|
cStringBufferLength = len(prql) * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// preallocate the buffer
|
||||||
|
cstr := C.CString(strings.Repeat(" ", cStringBufferLength))
|
||||||
|
defer C.free(unsafe.Pointer(cstr))
|
||||||
|
|
||||||
|
// convert the PRQL query to SQL
|
||||||
|
res := C.to_sql(C.CString(prql), cstr)
|
||||||
|
if res == 0 {
|
||||||
|
return C.GoString(cstr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New(C.GoString(cstr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJSON converts a PRQL query to JSON
|
||||||
|
func ToJSON(prql string) (string, error) {
|
||||||
|
// buffer length should not be less than 1K because we may get an error
|
||||||
|
cStringBufferLength := 1024
|
||||||
|
if len(prql)*3 > cStringBufferLength {
|
||||||
|
cStringBufferLength = len(prql) * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// preallocate the buffer
|
||||||
|
cstr := C.CString(strings.Repeat(" ", cStringBufferLength))
|
||||||
|
defer C.free(unsafe.Pointer(cstr))
|
||||||
|
|
||||||
|
// convert the PRQL query to SQL
|
||||||
|
res := C.to_json(C.CString(prql), cstr)
|
||||||
|
if res == 0 {
|
||||||
|
return C.GoString(cstr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New(C.GoString(cstr))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
|
||||||
|
The C & C++ header files `prqlc.h` & `prqlc.hpp` were generated using
|
||||||
|
[cbindgen](https://github.com/eqrion/cbindgen). To generate a new one run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
task build-prqlc-c-header
|
||||||
|
```
|
||||||
|
|
||||||
|
...or copy & paste the commands from the Taskfile.
|
@ -0,0 +1,14 @@
|
|||||||
|
language = "C"
|
||||||
|
|
||||||
|
header = '''/*
|
||||||
|
* PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement
|
||||||
|
*
|
||||||
|
* License: Apache-2.0
|
||||||
|
* Website: https://prql-lang.org/
|
||||||
|
*/'''
|
||||||
|
|
||||||
|
autogen_warning = "/* This file is autogenerated. Do not modify this file manually. */"
|
||||||
|
|
||||||
|
namespace = "prqlc"
|
||||||
|
|
||||||
|
after_includes = '#define FFI_SCOPE "PRQL"'
|
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* PRQL is a modern language for transforming data — a simple, powerful,
|
||||||
|
* pipelined SQL replacement
|
||||||
|
*
|
||||||
|
* License: Apache-2.0
|
||||||
|
* Website: https://prql-lang.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* This file is autogenerated. Do not modify this file manually. */
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#define FFI_SCOPE "PRQL"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile message kind. Currently only Error is implemented.
|
||||||
|
*/
|
||||||
|
typedef enum MessageKind {
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
Lint,
|
||||||
|
} MessageKind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier of a location in source.
|
||||||
|
* Contains offsets in terms of chars.
|
||||||
|
*/
|
||||||
|
typedef struct Span {
|
||||||
|
size_t start;
|
||||||
|
size_t end;
|
||||||
|
} Span;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Location within a source file.
|
||||||
|
*/
|
||||||
|
typedef struct SourceLocation {
|
||||||
|
size_t start_line;
|
||||||
|
size_t start_col;
|
||||||
|
size_t end_line;
|
||||||
|
size_t end_col;
|
||||||
|
} SourceLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile result message.
|
||||||
|
*
|
||||||
|
* Calling code is responsible for freeing all memory allocated
|
||||||
|
* for fields as well as strings.
|
||||||
|
*/
|
||||||
|
typedef struct Message {
|
||||||
|
/**
|
||||||
|
* Message kind. Currently only Error is implemented.
|
||||||
|
*/
|
||||||
|
enum MessageKind kind;
|
||||||
|
/**
|
||||||
|
* Machine-readable identifier of the error
|
||||||
|
*/
|
||||||
|
const char *const *code;
|
||||||
|
/**
|
||||||
|
* Plain text of the error
|
||||||
|
*/
|
||||||
|
const char *reason;
|
||||||
|
/**
|
||||||
|
* A list of suggestions of how to fix the error
|
||||||
|
*/
|
||||||
|
const char *const *hint;
|
||||||
|
/**
|
||||||
|
* Character offset of error origin within a source file
|
||||||
|
*/
|
||||||
|
const struct Span *span;
|
||||||
|
/**
|
||||||
|
* Annotated code, containing cause and hints.
|
||||||
|
*/
|
||||||
|
const char *const *display;
|
||||||
|
/**
|
||||||
|
* Line and column number of error origin within a source file
|
||||||
|
*/
|
||||||
|
const struct SourceLocation *location;
|
||||||
|
} Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of compilation.
|
||||||
|
*/
|
||||||
|
typedef struct CompileResult {
|
||||||
|
const char *output;
|
||||||
|
const struct Message *messages;
|
||||||
|
size_t messages_len;
|
||||||
|
} CompileResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compilation options
|
||||||
|
*/
|
||||||
|
typedef struct Options {
|
||||||
|
/**
|
||||||
|
* Pass generated SQL string trough a formatter that splits it
|
||||||
|
* into multiple lines and prettifies indentation and spacing.
|
||||||
|
*
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
|
bool format;
|
||||||
|
/**
|
||||||
|
* Target and dialect to compile to.
|
||||||
|
*
|
||||||
|
* Defaults to `sql.any`, which uses `target` argument from the query header
|
||||||
|
* to determine the SQL dialect.
|
||||||
|
*/
|
||||||
|
char *target;
|
||||||
|
/**
|
||||||
|
* Emits the compiler signature as a comment after generated SQL
|
||||||
|
*
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
|
bool signature_comment;
|
||||||
|
} Options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile a PRQL string into a SQL string.
|
||||||
|
*
|
||||||
|
* This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without
|
||||||
|
* converting to JSON between each of the functions.
|
||||||
|
*
|
||||||
|
* See `Options` struct for available compilation options.
|
||||||
|
*
|
||||||
|
* # Safety
|
||||||
|
*
|
||||||
|
* This function assumes zero-terminated input strings.
|
||||||
|
* Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
* by calling `result_destroy`.
|
||||||
|
*/
|
||||||
|
struct CompileResult compile(const char *prql_query,
|
||||||
|
const struct Options *options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build PL AST from a PRQL string. PL in documented in the
|
||||||
|
* [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).
|
||||||
|
*
|
||||||
|
* Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.
|
||||||
|
*
|
||||||
|
* Returns 0 on success and a negative number -1 on failure.
|
||||||
|
*
|
||||||
|
* # Safety
|
||||||
|
*
|
||||||
|
* This function assumes zero-terminated input strings.
|
||||||
|
* Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
* by calling `result_destroy`.
|
||||||
|
*/
|
||||||
|
struct CompileResult prql_to_pl(const char *prql_query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds variable references, validates functions calls, determines frames and
|
||||||
|
* converts PL to RQ. PL and RQ are documented in the [prqlc Rust
|
||||||
|
* crate](https://docs.rs/prqlc/latest/prqlc/ast).
|
||||||
|
*
|
||||||
|
* Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out`
|
||||||
|
* buffer.
|
||||||
|
*
|
||||||
|
* Returns 0 on success and a negative number -1 on failure.
|
||||||
|
*
|
||||||
|
* # Safety
|
||||||
|
*
|
||||||
|
* This function assumes zero-terminated input strings.
|
||||||
|
* Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
* by calling `result_destroy`.
|
||||||
|
*/
|
||||||
|
struct CompileResult pl_to_rq(const char *pl_json);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert RQ AST into an SQL string. RQ is documented in the
|
||||||
|
* [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).
|
||||||
|
*
|
||||||
|
* Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.
|
||||||
|
*
|
||||||
|
* Returns 0 on success and a negative number -1 on failure.
|
||||||
|
*
|
||||||
|
* # Safety
|
||||||
|
*
|
||||||
|
* This function assumes zero-terminated input strings.
|
||||||
|
* Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
* by calling `result_destroy`.
|
||||||
|
*/
|
||||||
|
struct CompileResult rq_to_sql(const char *rq_json,
|
||||||
|
const struct Options *options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a `CompileResult` once you are done with it.
|
||||||
|
*
|
||||||
|
* # Safety
|
||||||
|
*
|
||||||
|
* This function expects to be called exactly once after the call of any the
|
||||||
|
* functions that return `CompileResult`. No fields should be freed manually.
|
||||||
|
*/
|
||||||
|
void result_destroy(struct CompileResult res);
|
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* PRQL is a modern language for transforming data — a simple, powerful,
|
||||||
|
* pipelined SQL replacement
|
||||||
|
*
|
||||||
|
* License: Apache-2.0
|
||||||
|
* Website: https://prql-lang.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* This file is autogenerated. Do not modify this file manually. */
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <new>
|
||||||
|
#include <ostream>
|
||||||
|
#define FFI_SCOPE "PRQL"
|
||||||
|
|
||||||
|
namespace prqlc {
|
||||||
|
|
||||||
|
/// Compile message kind. Currently only Error is implemented.
|
||||||
|
enum class MessageKind {
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
Lint,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Identifier of a location in source.
|
||||||
|
/// Contains offsets in terms of chars.
|
||||||
|
struct Span {
|
||||||
|
size_t start;
|
||||||
|
size_t end;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Location within a source file.
|
||||||
|
struct SourceLocation {
|
||||||
|
size_t start_line;
|
||||||
|
size_t start_col;
|
||||||
|
size_t end_line;
|
||||||
|
size_t end_col;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Compile result message.
|
||||||
|
///
|
||||||
|
/// Calling code is responsible for freeing all memory allocated
|
||||||
|
/// for fields as well as strings.
|
||||||
|
struct Message {
|
||||||
|
/// Message kind. Currently only Error is implemented.
|
||||||
|
MessageKind kind;
|
||||||
|
/// Machine-readable identifier of the error
|
||||||
|
const char* const* code;
|
||||||
|
/// Plain text of the error
|
||||||
|
const char* reason;
|
||||||
|
/// A list of suggestions of how to fix the error
|
||||||
|
const char* const* hint;
|
||||||
|
/// Character offset of error origin within a source file
|
||||||
|
const Span* span;
|
||||||
|
/// Annotated code, containing cause and hints.
|
||||||
|
const char* const* display;
|
||||||
|
/// Line and column number of error origin within a source file
|
||||||
|
const SourceLocation* location;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Result of compilation.
|
||||||
|
struct CompileResult {
|
||||||
|
const char* output;
|
||||||
|
const Message* messages;
|
||||||
|
size_t messages_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Compilation options
|
||||||
|
struct Options {
|
||||||
|
/// Pass generated SQL string trough a formatter that splits it
|
||||||
|
/// into multiple lines and prettifies indentation and spacing.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
bool format;
|
||||||
|
/// Target and dialect to compile to.
|
||||||
|
///
|
||||||
|
/// Defaults to `sql.any`, which uses `target` argument from the query
|
||||||
|
/// header to determine the SQL dialect.
|
||||||
|
const char* target;
|
||||||
|
/// Emits the compiler signature as a comment after generated SQL
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
bool signature_comment;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
/// Compile a PRQL string into a SQL string.
|
||||||
|
///
|
||||||
|
/// This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without
|
||||||
|
/// converting to JSON between each of the functions.
|
||||||
|
///
|
||||||
|
/// See `Options` struct for available compilation options.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function assumes zero-terminated input strings.
|
||||||
|
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
/// by calling `result_destroy`.
|
||||||
|
CompileResult compile(const char* prql_query, const Options* options);
|
||||||
|
|
||||||
|
/// Build PL AST from a PRQL string. PL in documented in the
|
||||||
|
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).
|
||||||
|
///
|
||||||
|
/// Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.
|
||||||
|
///
|
||||||
|
/// Returns 0 on success and a negative number -1 on failure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function assumes zero-terminated input strings.
|
||||||
|
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
/// by calling `result_destroy`.
|
||||||
|
CompileResult prql_to_pl(const char* prql_query);
|
||||||
|
|
||||||
|
/// Finds variable references, validates functions calls, determines frames and
|
||||||
|
/// converts PL to RQ. PL and RQ are documented in the [prqlc Rust
|
||||||
|
/// crate](https://docs.rs/prqlc/latest/prqlc/ast).
|
||||||
|
///
|
||||||
|
/// Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out`
|
||||||
|
/// buffer.
|
||||||
|
///
|
||||||
|
/// Returns 0 on success and a negative number -1 on failure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function assumes zero-terminated input strings.
|
||||||
|
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
/// by calling `result_destroy`.
|
||||||
|
CompileResult pl_to_rq(const char* pl_json);
|
||||||
|
|
||||||
|
/// Convert RQ AST into an SQL string. RQ is documented in the
|
||||||
|
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).
|
||||||
|
///
|
||||||
|
/// Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.
|
||||||
|
///
|
||||||
|
/// Returns 0 on success and a negative number -1 on failure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function assumes zero-terminated input strings.
|
||||||
|
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
/// by calling `result_destroy`.
|
||||||
|
CompileResult rq_to_sql(const char* rq_json, const Options* options);
|
||||||
|
|
||||||
|
/// Destroy a `CompileResult` once you are done with it.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function expects to be called exactly once after the call of any the
|
||||||
|
/// functions that return `CompileResult`. No fields should be freed manually.
|
||||||
|
void result_destroy(CompileResult res);
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
} // namespace prqlc
|
@ -0,0 +1,346 @@
|
|||||||
|
#![cfg(not(target_family = "wasm"))]
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use libc::{c_char, size_t};
|
||||||
|
use prqlc::{ErrorMessage, ErrorMessages};
|
||||||
|
use prqlc::Target;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::panic;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// Compile a PRQL string into a SQL string.
|
||||||
|
///
|
||||||
|
/// This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without converting to JSON
|
||||||
|
/// between each of the functions.
|
||||||
|
///
|
||||||
|
/// See `Options` struct for available compilation options.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function assumes zero-terminated input strings.
|
||||||
|
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
/// by calling `result_destroy`.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn compile(
|
||||||
|
prql_query: *const c_char,
|
||||||
|
options: *const Options,
|
||||||
|
) -> CompileResult {
|
||||||
|
let prql_query: String = c_str_to_string(prql_query);
|
||||||
|
|
||||||
|
let options = options.as_ref().map(convert_options).transpose();
|
||||||
|
|
||||||
|
let result = options
|
||||||
|
.and_then(|opts| {
|
||||||
|
panic::catch_unwind(|| {
|
||||||
|
Ok(prql_query.as_str())
|
||||||
|
.and_then(prqlc::prql_to_pl)
|
||||||
|
.and_then(prqlc::pl_to_rq)
|
||||||
|
.and_then(|rq| prqlc::rq_to_sql(rq, &opts.unwrap_or_default()))
|
||||||
|
}).map_err(|p| ErrorMessages {
|
||||||
|
inner: vec![ErrorMessage {
|
||||||
|
kind: prqlc::MessageKind::Error,
|
||||||
|
code: None,
|
||||||
|
reason: format!("internal error: {:#?}", p),
|
||||||
|
hints: vec![],
|
||||||
|
span: None,
|
||||||
|
display: None,
|
||||||
|
location: None,
|
||||||
|
}]
|
||||||
|
})?
|
||||||
|
})
|
||||||
|
.map_err(|e| e.composed(&prql_query.into()));
|
||||||
|
|
||||||
|
result_into_c_str(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build PL AST from a PRQL string. PL in documented in the
|
||||||
|
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).
|
||||||
|
///
|
||||||
|
/// Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.
|
||||||
|
///
|
||||||
|
/// Returns 0 on success and a negative number -1 on failure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function assumes zero-terminated input strings.
|
||||||
|
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
/// by calling `result_destroy`.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn prql_to_pl(prql_query: *const c_char) -> CompileResult {
|
||||||
|
let prql_query: String = c_str_to_string(prql_query);
|
||||||
|
|
||||||
|
let result = Ok(prql_query.as_str())
|
||||||
|
.and_then(prqlc::prql_to_pl)
|
||||||
|
.and_then(|x| prqlc::json::from_pl(&x));
|
||||||
|
result_into_c_str(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds variable references, validates functions calls, determines frames and converts PL to RQ.
|
||||||
|
/// PL and RQ are documented in the
|
||||||
|
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ast).
|
||||||
|
///
|
||||||
|
/// Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out` buffer.
|
||||||
|
///
|
||||||
|
/// Returns 0 on success and a negative number -1 on failure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function assumes zero-terminated input strings.
|
||||||
|
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
/// by calling `result_destroy`.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn pl_to_rq(pl_json: *const c_char) -> CompileResult {
|
||||||
|
let pl_json: String = c_str_to_string(pl_json);
|
||||||
|
|
||||||
|
let result = Ok(pl_json.as_str())
|
||||||
|
.and_then(prqlc::json::to_pl)
|
||||||
|
.and_then(prqlc::pl_to_rq)
|
||||||
|
.and_then(|x| prqlc::json::from_rq(&x));
|
||||||
|
result_into_c_str(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert RQ AST into an SQL string. RQ is documented in the
|
||||||
|
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).
|
||||||
|
///
|
||||||
|
/// Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.
|
||||||
|
///
|
||||||
|
/// Returns 0 on success and a negative number -1 on failure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function assumes zero-terminated input strings.
|
||||||
|
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||||
|
/// by calling `result_destroy`.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rq_to_sql(
|
||||||
|
rq_json: *const c_char,
|
||||||
|
options: *const Options,
|
||||||
|
) -> CompileResult {
|
||||||
|
let rq_json: String = c_str_to_string(rq_json);
|
||||||
|
|
||||||
|
let options = options.as_ref().map(convert_options).transpose();
|
||||||
|
|
||||||
|
let result = options.and_then(|options| {
|
||||||
|
Ok(rq_json.as_str())
|
||||||
|
.and_then(prqlc::json::to_rq)
|
||||||
|
.and_then(|x| prqlc::rq_to_sql(x, &options.unwrap_or_default()))
|
||||||
|
});
|
||||||
|
result_into_c_str(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compilation options
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Options {
|
||||||
|
/// Pass generated SQL string trough a formatter that splits it
|
||||||
|
/// into multiple lines and prettifies indentation and spacing.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
pub format: bool,
|
||||||
|
|
||||||
|
/// Target and dialect to compile to.
|
||||||
|
///
|
||||||
|
/// Defaults to `sql.any`, which uses `target` argument from the query header to determine
|
||||||
|
/// the SQL dialect.
|
||||||
|
pub target: *mut c_char,
|
||||||
|
|
||||||
|
/// Emits the compiler signature as a comment after generated SQL
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
pub signature_comment: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of compilation.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct CompileResult {
|
||||||
|
pub output: *const libc::c_char,
|
||||||
|
pub messages: *const Message,
|
||||||
|
pub messages_len: size_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile message kind. Currently only Error is implemented.
|
||||||
|
#[repr(C)]
|
||||||
|
pub enum MessageKind {
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
Lint,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile result message.
|
||||||
|
///
|
||||||
|
/// Calling code is responsible for freeing all memory allocated
|
||||||
|
/// for fields as well as strings.
|
||||||
|
// Make sure to keep in sync with prqlc::ErrorMessage
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Message {
|
||||||
|
/// Message kind. Currently only Error is implemented.
|
||||||
|
pub kind: MessageKind,
|
||||||
|
/// Machine-readable identifier of the error
|
||||||
|
pub code: *const *const libc::c_char,
|
||||||
|
/// Plain text of the error
|
||||||
|
pub reason: *const libc::c_char,
|
||||||
|
/// A list of suggestions of how to fix the error
|
||||||
|
pub hint: *const *const libc::c_char,
|
||||||
|
/// Character offset of error origin within a source file
|
||||||
|
pub span: *const Span,
|
||||||
|
|
||||||
|
/// Annotated code, containing cause and hints.
|
||||||
|
pub display: *const *const libc::c_char,
|
||||||
|
/// Line and column number of error origin within a source file
|
||||||
|
pub location: *const SourceLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identifier of a location in source.
|
||||||
|
/// Contains offsets in terms of chars.
|
||||||
|
// Make sure to keep in sync with prqlc::Span
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Span {
|
||||||
|
pub start: size_t,
|
||||||
|
pub end: size_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Location within a source file.
|
||||||
|
// Make sure to keep in sync with prqlc::SourceLocation
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct SourceLocation {
|
||||||
|
pub start_line: size_t,
|
||||||
|
pub start_col: size_t,
|
||||||
|
|
||||||
|
pub end_line: size_t,
|
||||||
|
pub end_col: size_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroy a `CompileResult` once you are done with it.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function expects to be called exactly once after the call of any the functions
|
||||||
|
/// that return `CompileResult`. No fields should be freed manually.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn result_destroy(res: CompileResult) {
|
||||||
|
// This is required because we are allocating memory for
|
||||||
|
// strings, vectors and options.
|
||||||
|
// For strings and vectors this is required, but options may be
|
||||||
|
// able to live entirely within the struct, instead of the heap.
|
||||||
|
|
||||||
|
for i in 0..res.messages_len {
|
||||||
|
let e = &*res.messages.add(i);
|
||||||
|
|
||||||
|
if !e.code.is_null() {
|
||||||
|
drop(CString::from_raw(*e.code as *mut libc::c_char));
|
||||||
|
drop(Box::from_raw(e.code as *mut *const libc::c_char));
|
||||||
|
}
|
||||||
|
drop(CString::from_raw(e.reason as *mut libc::c_char));
|
||||||
|
if !e.hint.is_null() {
|
||||||
|
drop(CString::from_raw(*e.hint as *mut libc::c_char));
|
||||||
|
drop(Box::from_raw(e.hint as *mut *const libc::c_char));
|
||||||
|
}
|
||||||
|
if !e.span.is_null() {
|
||||||
|
drop(Box::from_raw(e.span as *mut Span));
|
||||||
|
}
|
||||||
|
if !e.display.is_null() {
|
||||||
|
drop(CString::from_raw(*e.display as *mut libc::c_char));
|
||||||
|
drop(Box::from_raw(e.display as *mut *const libc::c_char));
|
||||||
|
}
|
||||||
|
if !e.location.is_null() {
|
||||||
|
drop(Box::from_raw(e.location as *mut SourceLocation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(Vec::from_raw_parts(
|
||||||
|
res.messages as *mut i8,
|
||||||
|
res.messages_len,
|
||||||
|
res.messages_len,
|
||||||
|
));
|
||||||
|
drop(CString::from_raw(res.output as *mut libc::c_char));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn result_into_c_str(result: Result<String, ErrorMessages>) -> CompileResult {
|
||||||
|
match result {
|
||||||
|
Ok(output) => CompileResult {
|
||||||
|
output: convert_string(output),
|
||||||
|
messages: ::std::ptr::null_mut(),
|
||||||
|
messages_len: 0,
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
let mut errors = Vec::with_capacity(err.inner.len());
|
||||||
|
errors.extend(err.inner.into_iter().map(|e| Message {
|
||||||
|
kind: MessageKind::Error,
|
||||||
|
code: option_to_ptr(e.code.map(convert_string)),
|
||||||
|
reason: convert_string(e.reason),
|
||||||
|
hint: option_to_ptr(if e.hints.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(convert_string(e.hints.join("\n")))
|
||||||
|
}),
|
||||||
|
span: option_to_ptr(e.span.map(convert_span)),
|
||||||
|
display: option_to_ptr(e.display.map(convert_string)),
|
||||||
|
location: option_to_ptr(e.location.map(convert_source_location)),
|
||||||
|
}));
|
||||||
|
CompileResult {
|
||||||
|
output: CString::default().into_raw(),
|
||||||
|
messages_len: errors.len(),
|
||||||
|
messages: errors.leak().as_ptr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates the value on the heap and returns a pointer to it.
|
||||||
|
/// If the input is None, it returns null pointer.
|
||||||
|
fn option_to_ptr<T>(o: Option<T>) -> *const T {
|
||||||
|
match o {
|
||||||
|
Some(x) => {
|
||||||
|
let b = Box::new(x);
|
||||||
|
Box::into_raw(b)
|
||||||
|
}
|
||||||
|
None => ::std::ptr::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_string(x: String) -> *const libc::c_char {
|
||||||
|
CString::new(x).unwrap_or_default().into_raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_span(x: prqlc::Span) -> Span {
|
||||||
|
Span {
|
||||||
|
start: x.start,
|
||||||
|
end: x.end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_source_location(x: prqlc::SourceLocation) -> SourceLocation {
|
||||||
|
SourceLocation {
|
||||||
|
start_line: x.start.0,
|
||||||
|
start_col: x.start.1,
|
||||||
|
end_line: x.end.0,
|
||||||
|
end_col: x.end.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn c_str_to_string(c_str: *const c_char) -> String {
|
||||||
|
// inefficient, but simple
|
||||||
|
CStr::from_ptr(c_str).to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_options(o: &Options) -> Result<prqlc::Options, prqlc::ErrorMessages> {
|
||||||
|
let target = if !o.target.is_null() {
|
||||||
|
Some(unsafe { c_str_to_string(o.target) })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let target = target
|
||||||
|
.as_deref()
|
||||||
|
.filter(|x| !x.is_empty())
|
||||||
|
.unwrap_or("sql.any");
|
||||||
|
|
||||||
|
let target = Target::from_str(target).map_err(prqlc::ErrorMessages::from)?;
|
||||||
|
|
||||||
|
Ok(prqlc::Options {
|
||||||
|
format: o.format,
|
||||||
|
target,
|
||||||
|
signature_comment: o.signature_comment,
|
||||||
|
// TODO: add support for this
|
||||||
|
color: false,
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
export TZ=UTC
|
||||||
|
export YES_COLOR=1
|
||||||
|
unset XDG_CONFIG_HOME
|
||||||
|
|
||||||
|
run_cap_test ${lnav_test} -n \
|
||||||
|
-c ";from db.access_log | take 1" \
|
||||||
|
${test_dir}/logfile_access_log.0
|
||||||
|
|
||||||
|
run_cap_test ${lnav_test} -n \
|
||||||
|
-c ";from db.access_log | take abc" \
|
||||||
|
${test_dir}/logfile_access_log.0
|
Loading…
Reference in New Issue