Skip to content

Deparse #66

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: 15-latest
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@
"src/sync.cc",
"src/async.cc"
],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
'cflags!': ['-fno-exceptions', '-O3'],
'cflags_cc!': ['-fno-exceptions', '-O3'],
'include_dirs': [
"libpg_query/include",
"<!@(node -p \"require('node-addon-api').include\")"
"<!@(node -p \"require('node-addon-api').include\")",
"<!(pkg-config --variable=includedir protobuf)"
],
'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],
'conditions': [
['OS=="linux"', {
"libraries": [ "-L<!(pwd)/libpg_query/linux", "-lpg_query" ],
"libraries": [
"-L<!(pwd)/libpg_query/linux",
"-lpg_query",
"<!(pkg-config --variable=libdir protobuf)/libprotobuf.a"
],
"actions": [
{
"outputs": ['libpg_query/include/pg_query.h'],
Expand All @@ -28,7 +33,11 @@
],
}],
['OS=="mac"', {
"libraries": [ "-L<!(pwd)/libpg_query/osx", "-lpg_query" ],
"libraries": [
"-L<!(pwd)/libpg_query/osx",
"-lpg_query",
"<!(pkg-config --variable=libdir protobuf)/libprotobuf.a"
],
"xcode_settings": {
"CLANG_CXX_LIBRARY": "libc++",
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
Expand Down
12 changes: 12 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ module.exports = {
});
},

deparseQuery(ast) {
return new Promise((resolve, reject) => {
PgQuery.deparseQueryAsync(JSON.stringify(ast), (err, result) => {
err ? reject(err) : resolve(result);
});
});
},

parsePlPgSQL(query) {
return new Promise((resolve, reject) => {
PgQuery.parsePlPgSQLAsync(query, (err, result) => {
Expand All @@ -21,6 +29,10 @@ module.exports = {
return JSON.parse(PgQuery.parseQuerySync(query));
},

deparseQuerySync(ast) {
return PgQuery.deparseQuerySync(JSON.stringify(ast));
},

parsePlPgSQLSync(query) {
return JSON.parse(PgQuery.parsePlPgSQLSync(query));
},
Expand Down
Empty file.
3 changes: 2 additions & 1 deletion script/buildAddon.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ unset MFLAGS

# Adaptively build for macOS or Linux
if [ "$(uname)" == "Darwin" ]; then
make CFLAGS='-mmacosx-version-min=10.7' PG_CFLAGS='-mmacosx-version-min=10.7' $makeTarget
make CFLAGS='-mmacosx-version-min=10.7' PG_CFLAGS='-mmacosx-version-min=10.7' USE_PROTOBUF_CPP=1 $makeTarget
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
make CFLAGS='' PG_CFLAGS='' $makeTarget
fi
Expand Down Expand Up @@ -61,6 +61,7 @@ fi

# Copy header
cp $(pwd)/pg_query.h $rDIR/libpg_query/include/
cp $(pwd)/protobuf/pg_query.pb.h $rDIR/libpg_query/include/protobuf

# Cleanup: revert to original directory and remove the temp
cd "$rDIR"
Expand Down
10 changes: 10 additions & 0 deletions src/addon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
Napi::Function::New(env, ParseQueryAsync)
);

exports.Set(
Napi::String::New(env, "deparseQuerySync"),
Napi::Function::New(env, DeparseQuerySync)
);

exports.Set(
Napi::String::New(env, "deparseQueryAsync"),
Napi::Function::New(env, DeparseQueryAsync)
);

exports.Set(
Napi::String::New(env, "parsePlPgSQLSync"),
Napi::Function::New(env, ParsePlPgSQLSync)
Expand Down
45 changes: 45 additions & 0 deletions src/async.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,43 @@ class QueryWorker : public Napi::AsyncWorker {
PgQueryParseResult result;
};

class DeparseQueryWorker : public Napi::AsyncWorker {
public:
DeparseQueryWorker(Napi::Function& callback, const std::string& ast)
: Napi::AsyncWorker(callback), ast(ast) {}
~DeparseQueryWorker() {}

// Executed inside the worker-thread.
// It is not safe to access JS engine data structure
// here, so everything we need for input and output
// should go on `this`.
void Execute () {
auto protobufResult = json_to_protobuf_parse_result(ast);

if (protobufResult.error) {
result.error = protobufResult.error;
} else {
result = pg_query_deparse_protobuf(protobufResult.protobuf);
}
}

// Executed when the async work is complete
// this function will be run inside the main event loop
// so it is safe to use JS engine data again
void OnOK() {
Napi::HandleScope scope(Env());
try {
Callback().Call({Env().Undefined(), QueryDeparseResult(Env(), result) });
} catch (const Napi::Error& e) {
Callback().Call({ e.Value(), Env().Undefined() });
}
}

private:
std::string ast;
PgQueryDeparseResult result;
};

class PgPlQSLWorker : public Napi::AsyncWorker {
public:
PgPlQSLWorker(Napi::Function& callback, const std::string& query)
Expand Down Expand Up @@ -105,6 +142,14 @@ Napi::Value ParseQueryAsync(const Napi::CallbackInfo& info) {
return info.Env().Undefined();
}

Napi::Value DeparseQueryAsync(const Napi::CallbackInfo& info) {
std::string ast = info[0].As<Napi::String>();
Napi::Function callback = info[1].As<Napi::Function>();
DeparseQueryWorker* worker = new DeparseQueryWorker(callback, ast);
worker->Queue();
return info.Env().Undefined();
}

Napi::Value ParsePlPgSQLAsync(const Napi::CallbackInfo& info) {
std::string query = info[0].As<Napi::String>();
Napi::Function callback = info[1].As<Napi::Function>();
Expand Down
1 change: 1 addition & 0 deletions src/async.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <napi.h>

Napi::Value ParseQueryAsync(const Napi::CallbackInfo& info);
Napi::Value DeparseQueryAsync(const Napi::CallbackInfo& info);
Napi::Value ParsePlPgSQLAsync(const Napi::CallbackInfo& info);
Napi::Value FingerprintAsync(const Napi::CallbackInfo& info);
49 changes: 48 additions & 1 deletion src/helpers.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include "helpers.h" // NOLINT(build/include)
#include "pg_query.h"
#include "helpers.h"
#include "protobuf/pg_query.pb.h"
#include <napi.h>
#include <google/protobuf/util/json_util.h>

Napi::Error CreateError(Napi::Env env, const PgQueryError& err)
{
Expand All @@ -27,6 +28,19 @@ Napi::String QueryParseResult(Napi::Env env, const PgQueryParseResult& result)
return returnVal;
}

Napi::String QueryDeparseResult(Napi::Env env, const PgQueryDeparseResult& result)
{
if (result.error) {
auto throwVal = CreateError(env, *result.error);
pg_query_free_deparse_result(result);
throw throwVal;
}

auto returnVal = Napi::String::New(env, result.query);
pg_query_free_deparse_result(result);
return returnVal;
}

Napi::String PlPgSQLParseResult(Napi::Env env, const PgQueryPlpgsqlParseResult& result)
{
if (result.error) {
Expand All @@ -53,3 +67,36 @@ Napi::String FingerprintResult(Napi::Env env, const PgQueryFingerprintResult & r
pg_query_free_fingerprint_result(result);
return returnVal;
}

JsonToProtobufResult json_to_protobuf_parse_result(const std::string& json) {
JsonToProtobufResult result;

pg_query::ParseResult parse_result;
google::protobuf::util::JsonParseOptions options;

auto status = google::protobuf::util::JsonStringToMessage(json, &parse_result, options);

if (!status.ok()) {
auto error = (PgQueryError *)malloc(sizeof(PgQueryError));
error->message = strdup("Input AST did not match expected format");
error->filename = strdup("");
error->funcname = strdup("");
result.error = error;

return result;
}

std::string output;
parse_result.SerializeToString(&output);

PgQueryProtobuf protobuf;

protobuf.data = (char*) calloc(output.size(), sizeof(char));
memcpy(protobuf.data, output.data(), output.size());
protobuf.len = output.size();

result.protobuf = protobuf;
result.error = NULL;

return result;
}
8 changes: 8 additions & 0 deletions src/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@

Napi::Error CreateError(Napi::Env env, const PgQueryError& err);
Napi::String QueryParseResult(Napi::Env env, const PgQueryParseResult& result);
Napi::String QueryDeparseResult(Napi::Env env, const PgQueryDeparseResult& result);
Napi::String PlPgSQLParseResult(Napi::Env env, const PgQueryPlpgsqlParseResult& result);
Napi::String FingerprintResult(Napi::Env env, const PgQueryFingerprintResult & result);

typedef struct {
PgQueryProtobuf protobuf;
PgQueryError* error;
} JsonToProtobufResult;

JsonToProtobufResult json_to_protobuf_parse_result(const std::string& json);
15 changes: 15 additions & 0 deletions src/sync.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ Napi::String ParseQuerySync(const Napi::CallbackInfo& info) {
return QueryParseResult(info.Env(), result);
}

Napi::String DeparseQuerySync(const Napi::CallbackInfo& info) {
std::string ast = info[0].As<Napi::String>();
auto protobufResult = json_to_protobuf_parse_result(ast);
PgQueryDeparseResult result;

if (protobufResult.error) {
result.query = (char*)malloc(0);
result.error = protobufResult.error;
} else {
result = pg_query_deparse_protobuf(protobufResult.protobuf);
}

return QueryDeparseResult(info.Env(), result);
}

Napi::String ParsePlPgSQLSync(const Napi::CallbackInfo& info) {
std::string query = info[0].As<Napi::String>();
PgQueryPlpgsqlParseResult result = pg_query_parse_plpgsql(query.c_str());
Expand Down
1 change: 1 addition & 0 deletions src/sync.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <napi.h>

Napi::String ParseQuerySync(const Napi::CallbackInfo& info);
Napi::String DeparseQuerySync(const Napi::CallbackInfo& info);
Napi::String ParsePlPgSQLSync(const Napi::CallbackInfo& info);
Napi::String FingerprintSync(const Napi::CallbackInfo& info);
45 changes: 44 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe("Queries", () => {
true
);
expect(selectedDatas[2][0].ResTarget.val.A_Const.sval.sval).to.eq(
""
undefined
);
expect(selectedDatas[3]).to.have.lengthOf(2);
});
Expand All @@ -50,6 +50,24 @@ describe("Queries", () => {
});
});

describe("Sync Deparsing", () => {
it("should return the same input sql", () => {
const testQuery = "select * from books";
const ast = query.parseQuerySync(testQuery);
const deparsedQuery = query.deparseQuerySync(ast).toLowerCase();

expect(deparsedQuery).to.eq(testQuery);
});

it("should error with bad input", () => {
try {
query.deparseQuerySync({ wrongFormat: 123 }).toLowerCase();
} catch (e) {
expect(e.message).to.eq("Input AST did not match expected format");
}
});
});

describe("Async parsing", () => {
it("should return a promise resolving to same result", async () => {
const testQuery = "select * from john;";
Expand All @@ -73,6 +91,31 @@ describe("Queries", () => {
});
});

describe("Async deparsing", () => {
it("should return a promise resolving to same result", async () => {
const testQuery = "select * from books";
const ast = await query.parseQuery(testQuery);

const resPromise = query.deparseQuery(ast);
const res = await resPromise;

expect(resPromise).to.be.instanceof(Promise);
expect(res).to.deep.eq(query.deparseQuerySync(ast));
});

it("should reject on bad input", async () => {
return query.deparseQuery({ wrongFormat: 123 }).then(
() => {
throw new Error("should have rejected");
},
(e) => {
expect(e).instanceof(Error);
expect(e.message).to.eq("Input AST did not match expected format");
}
);
});
});

describe("Fingerprint", () => {
context("sync", () => {
it("should not fingerprint a bogus query", () => {
Expand Down