{"id":20799828,"url":"https://github.com/xnacly/sqleibniz","last_synced_at":"2025-12-24T12:15:41.886Z","repository":{"id":258178085,"uuid":"861621127","full_name":"xnacly/sqleibniz","owner":"xnacly","description":"Static analysis and LSP for SQL in Rust","archived":false,"fork":false,"pushed_at":"2025-12-18T12:48:29.000Z","size":307,"stargazers_count":88,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-12-20T06:44:46.442Z","etag":null,"topics":["lsp","sql","tooling"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xnacly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-23T08:33:38.000Z","updated_at":"2025-12-18T12:48:32.000Z","dependencies_parsed_at":"2024-10-20T23:14:38.159Z","dependency_job_id":null,"html_url":"https://github.com/xnacly/sqleibniz","commit_stats":null,"previous_names":["xnacly/sqleibniz"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/xnacly/sqleibniz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnacly%2Fsqleibniz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnacly%2Fsqleibniz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnacly%2Fsqleibniz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnacly%2Fsqleibniz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xnacly","download_url":"https://codeload.github.com/xnacly/sqleibniz/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnacly%2Fsqleibniz/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28002251,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-24T02:00:07.193Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lsp","sql","tooling"],"created_at":"2024-11-17T18:10:20.866Z","updated_at":"2025-12-24T12:15:41.880Z","avatar_url":"https://github.com/xnacly.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"# sqleibniz\n\nStatic analysis and LSP for SQL in Rust. Check for valid syntax, semantics and\nperform dynamic analysis.\n\n\u003e [!WARNING]  \n\u003e Sqleibniz is in early stages of development, please keep this in mind before\n\u003e creating issues. Contributions are always welcome 💗\n\n## Features\n\nSqleibniz is a command line tool to analyse sql statements by checking for their static and\ndynamic correctness. See below for a list of currently implemented features.\n\n### Supported features\n\n- [ ] static analysis (syntax and semantic analysis)\n  - [x] syntax analysis - sqleibniz aims to implement the syntax [sqlite understands](https://www.sqlite.org/lang.html)\n  - [ ] warn for sqlites [quirks](https://www.sqlite.org/quirks.html)\n  - [ ] do the used tables exist / were they created beforehand\n  - [ ] do the used columns exist / were they created beforehand\n  - [ ] do the used functions exist / were they created beforehand\n  - [ ] are all used types compatible\n- [ ] dynamic analysis (runtime analysis via embedded sqlite)\n  - [ ] assertions via `@sqleibniz::assert`\n  - [ ] were all tables and their columns created correctly (with correct storage classes)\n  - [ ] were all stmts executed successfully\n- [ ] pretty errors\n  - [x] faulty code display with line numbers\n  - [x] link to sqlite documentation for each diagnostic\n  - [x] ability to omit specific errors depending on their group (Rule)\n  - [x] highlighting the error in the faulty code snippet\n  - [x] explanation why the specific error was ommitted based on its Rule\n  - [x] syntax highlighting in terminal errors\n  - [ ] possible fix suggestions\n  - [x] suggestions for unknown and possible misspelled keywords\n- [ ] language server protocol\n  - [x] diagnostics for full sqleibniz analysis\n  - [ ] snippets\n  - [ ] intelligent completions\n- [ ] lua scripting\n  - [x] configure sqleibniz with lua\n  - [x] scripting to hook into node analysis for custom diagnostics\n  - [ ] execute hooks when encountering the defined node while analysing\n\n### Supported Sql statements\n\n| `sqlite` specification                                                     | syntax analysis | semantic analysis | Example                                                   |\n| -------------------------------------------------------------------------- | --------------- | ----------------- | --------------------------------------------------------- |\n| [`explain-stmt`](https://www.sqlite.org/lang_explain.html)                 | ✅              | ❌                | `EXPLAIN QUERY PLAN;`                                     |\n| [`alter-table-stmt`](https://www.sqlite.org/lang_altertable.html)          | ✅              | ❌                | `ALTER TABLE schema.table_name ADD new_column_name TEXT;` |\n| [`analyze-stmt`](https://www.sqlite.org/lang_analyze.html)                 | ✅              | ❌                | `ANALYZE my_table;`                                       |\n| [`attach-stmt`](https://www.sqlite.org/lang_attach.html)                   | ✅              | ❌                | `ATTACH DATABASE 'users.db' AS users;`                    |\n| [`begin-stmt`](https://www.sqlite.org/lang_transaction.html)               | ✅              | ❌                | `BEGIN DEFERRED TRANSACTION;`                             |\n| [`commit-stmt`](https://www.sqlite.org/lang_transaction.html)              | ✅              | ❌                | `END TRANSACTION;`                                        |\n| [`create-index-stmt`](https://www.sqlite.org/lang_createindex.html)        | ❌              | ❌                |                                                           |\n| [`create-table-stmt`](https://www.sqlite.org/lang_createtable.html)        | ❌              | ❌                |                                                           |\n| [`create-trigger-stmt`](https://www.sqlite.org/lang_createtrigger.html)    | ❌              | ❌                |                                                           |\n| [`create-view-stmt`](https://www.sqlite.org/lang_createview.html)          | ❌              | ❌                |                                                           |\n| [`create-virtual-table-stmt`](https://www.sqlite.org/lang_createvtab.html) | ❌              | ❌                |                                                           |\n| [`delete-stmt`](https://www.sqlite.org/lang_delete.html)                   | ❌              | ❌                |                                                           |\n| [`detach-stmt`](https://www.sqlite.org/lang_detach.html)                   | ✅              | ❌                | `DETACH DATABASE my_database`                             |\n| [`drop-index-stmt`](https://www.sqlite.org/lang_dropindex.html)            | ✅              | ❌                | `DROP INDEX my_index;`                                    |\n| [`drop-table-stmt`](https://www.sqlite.org/lang_droptable.html)            | ✅              | ❌                | `DROP TABLE my_table;`                                    |\n| [`drop-trigger-stmt`](https://www.sqlite.org/lang_droptrigger.html)        | ✅              | ❌                | `DROP TRIGGER my_trigger;`                                |\n| [`drop-view-stmt`](https://www.sqlite.org/lang_dropview.html)              | ✅              | ❌                | `DROP VIEW my_view;`                                      |\n| [`insert-stmt`](https://www.sqlite.org/lang_insert.html)                   | ❌              | ❌                |                                                           |\n| [`pragma-stmt`](https://www.sqlite.org/pragma.html)                        | ❌              | ❌                |                                                           |\n| [`reindex-stmt`](https://www.sqlite.org/lang_reindex.html)                 | ✅              | ❌                | `REINDEX my_schema.my_table`                              |\n| [`release-stmt`](https://www.sqlite.org/lang_savepoint.html)               | ✅              | ❌                | `RELEASE SAVEPOINT latest_savepoint`                      |\n| [`rollback-stmt`](https://www.sqlite.org/lang_transaction.html)            | ✅              | ❌                | `ROLLBACK TO latest_savepoint;`                           |\n| [`savepoint-stmt`](https://www.sqlite.org/lang_savepoint.html)             | ✅              | ❌                | `SAVEPOINT latest_savepoint`                              |\n| [`select-stmt`](https://www.sqlite.org/lang_select.html)                   | ❌              | ❌                |                                                           |\n| [`update-stmt`](https://www.sqlite.org/lang_update.html)                   | ❌              | ❌                |                                                           |\n| [`vacuum-stmt`](https://www.sqlite.org/lang_vacuum.html)                   | ✅              | ❌                | `VACUUM INTO 'repacked.db'`                               |\n\n## Installation\n\n### cargo\n\n```\ncargo install --git https://github.com/xnacly/sqleibniz\n```\n\n#### from source\n\n```shell\ngit clone https://github.com/xnacly/sqleibniz\ncargo install --path .\n```\n\n### via `make`\n\n\u003e this builds the project with cargo and moves the resulting binary to\n\u003e `/usr/bin/`.\n\n```shell\ngit clone https://github.com/xnacly/sqleibniz\nmake\n```\n\nUninstall via:\n\n```shell\nmake uninstall\n```\n\n\u003c!--## Language Server Protocol (lsp)\n\n\u003e [!WARNING]\n\u003e This feature is not yet implemented.\n\n### Setup in Neovim\n\n\u003e requires systemwide installation beforehand\n--\u003e\n\n## Command line interface usage\n\n```text\nLSP and analysis cli for sql. Check for valid syntax, semantics and perform dynamic analysis\n\nUsage: sqleibniz [OPTIONS] [PATHS]...\n\nArguments:\n  [PATHS]...\n          files to analyse\n\nOptions:\n  -i, --ignore-config\n          instruct sqleibniz to ignore the configuration, if found\n\n  -c, --config \u003cCONFIG\u003e\n          path to the configuration\n\n          [default: leibniz.toml]\n\n  -s, --silent\n          disable stdout/stderr output\n\n  -D \u003cDISABLE\u003e\n          disable diagnostics by their rules, all are enabled by default - this may change in the future\n\n          Possible values:\n          - no-content:                Source file is empty\n          - no-statements:             Source file is not empty but holds no statements\n          - unimplemented:             Source file contains constructs sqleibniz does not yet understand\n          - unknown-keyword:           Source file contains an unknown keyword\n          - bad-sqleibniz-instruction: Source file contains invalid sqleibniz instruction\n          - unterminated-string:       Source file contains an unterminated string\n          - unknown-character:         The source file contains an unknown character\n          - invalid-numeric-literal:   The source file contains an invalid numeric literal, either overflow or incorrect syntax\n          - invalid-blob:              The source file contains an invalid blob literal, either bad hex data (a-f,A-F,0-9) or incorrect syntax\n          - syntax:                    The source file contains a structure with incorrect syntax\n          - semicolon:                 The source file is missing a semicolon\n\n  -h, --help\n          Print help (see a summary with '-h'\n```\n\n### Configuration\n\nSqleibniz can be configured via a `leibniz.lua` file, this file has to be\naccessible to sqleibniz by existing at the path sqleibniz is invoked at.\nConsult [src/rules.rs](./src/rules.rs) for configuration documentation and\n[leibniz.lua](./leibniz.lua) for said example:\n\n````lua\n-- this is an example configuration, consult: https://www.lua.org/manual/5.4/\n-- or https://learnxinyminutes.com/docs/lua/ for syntax help and\n-- src/rules.rs::Config for all available options\nleibniz = {\n    disabled_rules = {\n        -- ignore sqleibniz specific diagnostics:\n        \"NoContent\",               -- source file is empty\n        \"NoStatements\",            -- source file contains no statements\n        \"Unimplemented\",           -- construct is not implemented yet\n        \"BadSqleibnizInstruction\", -- source file contains a bad sqleibniz instruction\n\n        -- ignore sqlite specific diagnostics:\n\n        -- \"UnknownKeyword\", -- an unknown keyword was encountered\n        -- \"UnterminatedString\", -- a not closed string was found\n        -- \"UnknownCharacter\", -- an unknown character was found\n        -- \"InvalidNumericLiteral\", -- an invalid numeric literal was found\n        -- \"InvalidBlob\", -- an invalid blob literal was found (either bad hex data or incorrect syntax)\n        -- \"Syntax\", -- a structure with incorrect syntax was found\n        -- \"Semicolon\", -- a semicolon is missing\n    },\n    -- sqleibniz allows for writing custom rules with lua\n    hooks = {\n        {\n            -- summarises the hooks content\n            name = \"idents should be lowercase\",\n            -- instructs sqleibniz which node to execute the `hook` for\n            node = \"literal\",\n            -- sqleibniz calls the hook function once it encounters a node name\n            -- matching the hook.node content\n            --\n            -- The `node` argument holds the following fields:\n            --\n            --```\n            --    node: {\n            --     kind: string,\n            --     content: string,\n            --     children: node[],\n            --    }\n            --```\n            --\n            hook = function(node)\n                if node.kind == \"ident\" then\n                    if string.match(node.content, \"%u\") then\n                        -- returing an error passes the diagnostic to sqleibniz,\n                        -- thus a pretty message with the name of the hook, the\n                        -- node it occurs and the message passed to error() is\n                        -- generated\n                        error(\"All idents should be lowercase\")\n                    end\n                end\n            end\n        },\n        {\n            name = \"idents shouldn't be longer than 12 characters\",\n            node = \"literal\",\n            hook = function(node)\n                local max_size = 12\n                if node.kind == \"ident\" then\n                    if string.len(node.content) \u003e= max_size then\n                        error(\"idents shouldn't be longer than \" .. max_size .. \" characters\")\n                    end\n                end\n            end\n        }\n    }\n}\n````\n\n### sqleibniz instructions\n\nA sqleibniz instrution is prefixed with `@sqleibniz::` and written inside of a\nsql single line comment.\n\n#### `expect`\n\nIn a similar fashion to ignoring diagnostics via the configuration in\n`leibniz.toml`, sqleibniz allows the user to expect diagnostics in the source\nfile and omit them on a statement by statement basis. To do so, a comment\ncontaining a sqleibniz instruction has to be issued:\n\n```sql\n-- will not cause a diagnostic\n-- @sqleibniz::expect \u003cexplanation for instruction usage here\u003e\n-- incorrect, because EXPLAIN wants a sql stmt\nEXPLAIN 25;\n\n-- will not cause a diagnostic\n-- @sqleibniz::expect \u003cexplanation for instruction usage here\u003e\n-- incorrect, because 'unknown_table' does not exist\nSELECT * FROM unknown_table;\n\n-- will cause a diagnostic\n-- incorrect, because EXPLAIN wants a sql stmt, not a literal\nEXPLAIN QUERY PLAN 25;\n```\n\nPassing the above file to `sqleibniz`:\n\n```text\nwarn: Ignoring the following diagnostics, as specified:\n -\u003e NoContent\n -\u003e NoStatements\n -\u003e Unimplemented\n -\u003e BadSqleibnizInstruction\n=============================== test.sql ===============================\nerror[Syntax]: Unexpected Literal\n -\u003e /home/teo/programming/sqleibniz/test.sql:13:20\n 11 | -- will cause a diagnostic\n 12 | -- incorrect, because EXPLAIN wants a sql stmt, not a literal\n 13 | EXPLAIN QUERY PLAN 25;\n    |                    ^^ error occurs here.\n    |\n    ~ note: Literal Number(25.0) disallowed at this point.\n  * Syntax: The source file contains a structure with incorrect syntax\n docs: https://www.sqlite.org/syntax/sql-stmt.html\n=============================== Summary ================================\n[-] test.sql:\n    1 Error(s) detected\n    0 Error(s) ignored\n\n=\u003e 0/1 Files verified successfully, 1 verification failed\n```\n\nOr syntax highlighted via [`highlight::highlight`](https://github.com/xNaCly/sqleibniz/blob/master/src/highlight/mod.rs#L50) for the terminal:\n\n![rendered by the terminal](https://github.com/user-attachments/assets/dd349d59-1107-4421-82e4-f95549b43e85)\n\n\n`@sqleibniz::expect` is implemented by inserting a token with the type\n`Type::InstructionExpect`. The parser encounters this token and consumes all\ntoken until a token with the type `Type::Semicolon` is found. Thus sqleibniz is\nskipping the analysis of the statement directly after the sqleibniz\ninstruction. A statement is terminated via `;`. `@sqleibniz::expect` therefore\nsupports ignoring diagnostics for statements spanning either a single line or\nmultiple lines.\n\n## Contribution\n\nContributions are always welcome \u003c3, but remember to test all features you contribute.\n\n### Local Dev env\n\n```shell\ngit clone git@github.com:xNaCly/sqleibniz.git\ncargo run example/*\n```\n\n### Debugging the parser\n\nRun sqleibniz via cargo with `--features trace` to enable the log of each\n`Parser.\u003cstmt_type\u003e_stmt` function as well as the resulting ast nodes. This\nallows for a deeper insight for deadlocks etc.\n\n```sql\nEXPLAIN VACUUM;\nEXPLAIN QUERY PLAN VACUUM my_big_schema INTO 'repacked.db';\n```\n\nFor instance, parsing the above SQL results in the generation and printing of a\nparser callstack and the resulting AST:\n\n```text\nsqleibniz master M :: cargo run --features trace -- -i test.sql\n    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s\n     Running `target/debug/sqleibniz -i test.sql`\n============================== CALLSTACK ===============================\n ↳ Parser::parse(Some(Keyword(EXPLAIN)))\n  ↳ Parser::sql_stmt_list(Some(Keyword(EXPLAIN)))\n   ↳ Parser::sql_stmt_prefix(Some(Keyword(EXPLAIN)))\n    ↳ Parser::sql_stmt(Some(Keyword(VACUUM)))\n     ↳ Parser::vacuum_stmt(Some(Keyword(VACUUM)))\n    ↳ Parser::sql_stmt_prefix(Some(Keyword(EXPLAIN)))\n     ↳ Parser::sql_stmt(Some(Keyword(VACUUM)))\n      ↳ Parser::vacuum_stmt(Some(Keyword(VACUUM)))\n================================= AST ==================================\n- Explain(Keyword(EXPLAIN)) [child=Vacuum { t: Token { ttype: Keyword(VACUUM), start: 8, end: 14, line: 0 }, schema_name: None, filename: None }]\n- Explain(Keyword(EXPLAIN)) [child=Vacuum { t: Token { ttype: Keyword(VACUUM), start: 19, end: 25, line: 1 }, schema_name: Some(Token { ttype: Ident(\"my_big_schema\"), start: 26, end: 39, line: 1 }), filename: Some(Token { ttype: String(\"\nrepacked.db\"), start: 45, end: 58, line: 1 }) }]\ntook: [122.011µs]\n=============================== Summary ================================\n[+] test.sql:\n    0 Error(s) detected\n    0 Error(s) ignored\n\n=\u003e 1/1 Files verified successfully, 0 verification failed.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxnacly%2Fsqleibniz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxnacly%2Fsqleibniz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxnacly%2Fsqleibniz/lists"}