diff --git a/.gitignore b/.gitignore index afb9e55..a3ce7c5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ subprojects/* *.flatpak *.dot io.github.ppvan.tarug.flatpak + +.vscode/* diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 664e7a4..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // 使用 IntelliSense 了解相关属性。 - // 悬停以查看现有属性的描述。 - // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 - // Debug with CoreLLDB extension - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Launch", - "program": "${workspaceFolder}/build/src/${workspaceFolderBasename}", - "cwd": "${workspaceFolder}", - "preLaunchTask": "Compile" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 51dc160..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "Meson (Flatpak): Run Unit test", - "type": "shell", - "options": { - // Match meson buildPath - "cwd": "${workspaceFolder}/_build" - }, - "group": "test", - // mesonbuild.mesonPath - "command": "${workspaceFolder}/.flatpak/meson.sh test", - } - ] -} \ No newline at end of file diff --git a/pkgs/flatpak/io.github.ppvan.tarug.yml b/pkgs/flatpak/io.github.ppvan.tarug.yml index dcedb3e..f0dc976 100644 --- a/pkgs/flatpak/io.github.ppvan.tarug.yml +++ b/pkgs/flatpak/io.github.ppvan.tarug.yml @@ -59,15 +59,23 @@ modules: - type: archive url: https://ftp.postgresql.org/pub/source/v17.0/postgresql-17.0.tar.bz2 sha256: 7e276131c0fdd6b62588dbad9b3bb24b8c3498d5009328dba59af16e819109de - - name: libcsv - buildsystem: meson + - name: libpg_query + buildsystem: simple + build-commands: + - make + - make install prefix=/app sources: - type: git - url: https://github.com/ppvan/libcsv - commit: c8f01b8b4aa8d8156a624405b8a4c646e3f6efaa - - name: libquery-vala - buildsystem: meson + url: https://github.com/pganalyze/libpg_query + tag: 17-6.0.0 + - name: libcsv + buildsystem: simple + build-commands: + - ./configure --prefix=/app + - make + - make check + - make install sources: - - type: git - url: https://github.com/ppvan/pg_query_vala - commit: 1672a38f04d4c8ba61f0ddc11e1203c824e704e9 + - type: archive + url: http://deb.debian.org/debian/pool/main/libc/libcsv/libcsv_3.0.3+dfsg.orig.tar.gz + sha256: 06fddfaca720a4be7603bad63eb1833bca5b6c5a69b42b1c518a02cda2a73aef diff --git a/src/meson.build b/src/meson.build index 44d89b3..a71d2fa 100644 --- a/src/meson.build +++ b/src/meson.build @@ -86,6 +86,8 @@ configure_file(output: 'config.h', configuration: conf) cc = meson.get_compiler('c') math_dep = cc.find_library('m', required: false) +pg_query_dep = cc.find_library('pg_query', has_headers: ['pg_query.h']) +csv_dep = cc.find_library('csv', has_headers: ['csv.h']) tarug_deps = [ # assume exists typical gtk system @@ -96,10 +98,10 @@ tarug_deps = [ dependency('gee-0.8'), dependency('gtksourceview-5', version: '>= 5.0'), dependency('libpq', version: '>= 15.3'), - math_dep, dependency('sqlite3'), - dependency('pgquery-vala'), - cc.find_library('csv', has_headers: ['csv.h'], required: true), + math_dep, + csv_dep, + pg_query_dep, valac.find_library('config', dirs: vapi_dir), ] @@ -109,6 +111,8 @@ add_project_arguments( '--vapidir', vapi_dir, '--gresourcesdir', tarug_resources_dir, '--enable-deprecated', + # IIUC, cc.find_library will not intergrate with vala compiler, hence the custom --pkg option + '--pkg', 'pg_query', '--pkg', 'csv' ], language: 'vala', diff --git a/src/services/SQLCompletionService.vala b/src/services/SQLCompletionService.vala index 1bb6b50..3d04f40 100644 --- a/src/services/SQLCompletionService.vala +++ b/src/services/SQLCompletionService.vala @@ -180,7 +180,7 @@ namespace Tarug { foreach (var item in keywords) { var propose_query = cur_stmt.strip() + " " + item.value; - if (PGQuery.split_statement(propose_query, true) != null) { + if (is_sql_query (propose_query)) { final.append(item); } } diff --git a/src/ui/editor/QueryEditor.vala b/src/ui/editor/QueryEditor.vala index edc63a3..524aa40 100644 --- a/src/ui/editor/QueryEditor.vala +++ b/src/ui/editor/QueryEditor.vala @@ -133,11 +133,18 @@ namespace Tarug { } private void highlight_current_query (){ - var stmts = PGQuery.split_statement(buffer.text); + var raw_query = buffer.text; + var stmts = PgQuery.split_with_scanner(raw_query); this.clear_highlight(); - stmts.foreach((token) => { - var start = token.location; - var end = token.location + token.statement.length; + + for (var i = 0; i < stmts.n_stmts; i++) { + if (stmts.error != null) { + return; + } + var current_statement = stmts.stmts[i]; + + var start = current_statement->stmt_location; + var end = start + current_statement->stmt_len; // debug ("[%d, %d], %s", token.location, token.end, token.value); @@ -154,7 +161,7 @@ namespace Tarug { if (start < buffer.cursor_position && buffer.cursor_position <= end + 1) { // Double-check with strict mode. string statement = buffer.get_text(iter1, iter2, false); - if (PGQuery.split_statement(statement, true) == null) { + if (!is_sql_query(statement)) { return; } @@ -165,13 +172,12 @@ namespace Tarug { buffer.apply_tag_by_name(LIGHT_TAG, iter1, iter2); } - // Important - query_viewmodel.selected_query_changed(token.statement); + query_viewmodel.selected_query_changed(statement); } else { buffer.remove_tag_by_name(DARK_TAG, iter1, iter2); buffer.remove_tag_by_name(LIGHT_TAG, iter1, iter2); } - }); + } } private inline void clear_highlight (){ diff --git a/src/utils/types.vala b/src/utils/types.vala index 3705385..dd8311c 100644 --- a/src/utils/types.vala +++ b/src/utils/types.vala @@ -60,6 +60,11 @@ namespace Tarug { return local_time; } + public bool is_sql_query(string input) { + var result = PgQuery.parse(input); + return result.error == null; + } + public class Vec: Object { static int DEFAULT_CAPACITY = 64; diff --git a/src/vapi/pg_query.vapi b/src/vapi/pg_query.vapi new file mode 100644 index 0000000..9feb1e2 --- /dev/null +++ b/src/vapi/pg_query.vapi @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 Phạm Văn Phúc + */ + +[CCode (cprefix = "pg_query", cheader_filename = "pg_query.h")] +namespace PgQuery { + [CCode (cname = "PgQueryError", destroy_function = "")] + public struct Error { + public string message; + public string funcname; + public string filename; + public int lineno; + public int cursorpos; + public string context; + } + + [CCode (cname = "PgQueryProtobuf", destroy_function = "")] + public struct Protobuf { + public size_t len; + public string data; + } + + [CCode (cname = "PgQueryScanResult", destroy_function = "pg_query_free_scan_result")] + public struct ScanResult { + public Protobuf pbuf; + public string stderr_buffer; + public Error* error; + } + + [SimpleType] + [CCode (cname = "PgQueryParseResult", destroy_function = "pg_query_free_parse_result", has_type_id = false)] + public struct ParseResult { + public string parse_tree; + public string stderr_buffer; + public Error* error; + } + + [CCode (cname = "PgQueryProtobufParseResult", destroy_function = "pg_query_free_protobuf_parse_result")] + public struct ProtobufParseResult { + public Protobuf parse_tree; + public string stderr_buffer; + public Error* error; + } + + [CCode (cname = "PgQuerySplitStmt", destroy_function = "")] + public struct SplitStmt { + public int stmt_location; + public int stmt_len; + } + + [SimpleType] + [CCode (cname = "PgQuerySplitResult", destroy_function = "pg_query_free_split_result", has_type_id = false)] + public struct SplitResult { + public SplitStmt** stmts; + public int n_stmts; + public string stderr_buffer; + public Error* error; + } + + [CCode (cname = "PgQueryDeparseResult", destroy_function = "pg_query_free_deparse_result")] + public struct DeparseResult { + public string query; + public Error* error; + } + + [CCode (cname = "PgQueryPlpgsqlParseResult", destroy_function = "pg_query_free_plpgsql_parse_result")] + public struct PlpgsqlParseResult { + public string plpgsql_funcs; + public Error* error; + } + + [CCode (cname = "PgQueryFingerprintResult", destroy_function = "pg_query_free_fingerprint_result")] + public struct FingerprintResult { + public uint64 fingerprint; + public string fingerprint_str; + public string stderr_buffer; + public Error* error; + } + + [CCode (cname = "PgQueryNormalizeResult", destroy_function = "pg_query_free_normalize_result")] + public struct NormalizeResult { + public string normalized_query; + public Error* error; + } + + [CCode (cname = "PgQueryParseMode", cprefix = "PG_QUERY_PARSE_")] + public enum ParseMode { + DEFAULT, + TYPE_NAME, + PLPGSQL_EXPR, + PLPGSQL_ASSIGN1, + PLPGSQL_ASSIGN2, + PLPGSQL_ASSIGN3 + } + + // Constants + public const int PARSE_MODE_BITS; + public const int PARSE_MODE_BITMASK; + public const int DISABLE_BACKSLASH_QUOTE; + public const int DISABLE_STANDARD_CONFORMING_STRINGS; + public const int DISABLE_ESCAPE_STRING_WARNING; + + // Function bindings + [CCode (cname = "pg_query_normalize")] + public static NormalizeResult normalize(string input); + + [CCode (cname = "pg_query_normalize_utility")] + public static NormalizeResult normalize_utility(string input); + + [CCode (cname = "pg_query_scan")] + public static ScanResult scan(string input); + + [CCode (cname = "pg_query_parse")] + public static ParseResult parse(string input); + + [CCode (cname = "pg_query_parse_opts")] + public static ParseResult parse_opts(string input, int parser_options); + + [CCode (cname = "pg_query_parse_protobuf")] + public static ProtobufParseResult parse_protobuf(string input); + + [CCode (cname = "pg_query_parse_protobuf_opts")] + public static ProtobufParseResult parse_protobuf_opts(string input, int parser_options); + + [CCode (cname = "pg_query_parse_plpgsql")] + public static PlpgsqlParseResult parse_plpgsql(string input); + + [CCode (cname = "pg_query_fingerprint")] + public static FingerprintResult fingerprint(string input); + + [CCode (cname = "pg_query_fingerprint_opts")] + public static FingerprintResult fingerprint_opts(string input, int parser_options); + + [CCode (cname = "pg_query_split_with_scanner")] + public static SplitResult split_with_scanner(string input); + + [CCode (cname = "pg_query_split_with_parser")] + public static SplitResult split_with_parser(string input); + + [CCode (cname = "pg_query_deparse_protobuf")] + public static DeparseResult deparse_protobuf(Protobuf parse_tree); + + [CCode (cname = "pg_query_exit")] + public static void exit(); + + [CCode (cname = "PG_MAJORVERSION")] + public const string MAJORVERSION; + [CCode (cname = "PG_VERSION")] + public const string VERSION; + [CCode (cname = "PG_VERSION_NUM")] + public const int VERSION_NUM; +} \ No newline at end of file