├── clippy.toml ├── CLAUDE.md ├── crates ├── pgls_lexer │ ├── src │ │ └── codegen │ │ │ ├── mod.rs │ │ │ └── syntax_kind.rs │ ├── README.md │ └── Cargo.toml ├── pgls_query │ ├── src │ │ ├── iter_mut.rs │ │ ├── iter_ref.rs │ │ ├── node_structs.rs │ │ ├── node_mut.rs │ │ ├── node_ref.rs │ │ └── error.rs │ └── Cargo.toml ├── pgls_query_ext │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── pgls_cli │ ├── tests │ │ ├── fixtures │ │ │ ├── test.sql │ │ │ ├── traversal │ │ │ │ ├── bad.sql │ │ │ │ ├── another_bad.sql │ │ │ │ └── postgres-language-server.jsonc │ │ │ └── postgres-language-server.jsonc │ │ └── snapshots │ │ │ ├── assert_check__check_stdin_snapshot.snap │ │ │ ├── assert_check__check_github_reporter_snapshot.snap │ │ │ ├── assert_check__check_default_reporter_snapshot.snap │ │ │ └── assert_check__check_gitlab_reporter_snapshot.snap │ └── src │ │ ├── execute │ │ ├── stdin.rs │ │ └── mod.rs │ │ └── commands │ │ ├── clean.rs │ │ └── init.rs ├── pgls_splinter │ ├── vendor │ │ └── COMMIT_SHA.txt │ ├── tests │ │ └── snapshots │ │ │ ├── no_issues.snap │ │ │ ├── rls_disabled_in_public.snap │ │ │ ├── no_primary_key.snap │ │ │ ├── policy_exists_rls_disabled.snap │ │ │ └── unindexed_foreign_key.snap │ └── Cargo.toml ├── pgls_analyser │ ├── tests │ │ └── specs │ │ │ └── safety │ │ │ ├── banDropTable │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── transactionNesting │ │ │ ├── basic.sql │ │ │ ├── commit.sql │ │ │ ├── rollback.sql │ │ │ ├── begin_commit_combined.sql │ │ │ ├── basic.sql.snap │ │ │ ├── commit.sql.snap │ │ │ └── rollback.sql.snap │ │ │ ├── banDropDatabase │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── banTruncateCascade │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── banDropColumn │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── renamingTable │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── addingRequiredField │ │ │ ├── without_required.sql │ │ │ ├── basic.sql │ │ │ ├── with_default.sql │ │ │ ├── without_required.sql.snap │ │ │ └── with_default.sql.snap │ │ │ ├── preferBigInt │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── preferIdentity │ │ │ ├── alter_table.sql │ │ │ ├── basic.sql │ │ │ ├── bigserial.sql │ │ │ ├── valid.sql │ │ │ ├── valid.sql.snap │ │ │ ├── alter_table.sql.snap │ │ │ ├── basic.sql.snap │ │ │ └── bigserial.sql.snap │ │ │ ├── banCharField │ │ │ ├── bpchar.sql │ │ │ ├── alter_table.sql │ │ │ ├── basic.sql │ │ │ ├── varchar_valid.sql │ │ │ ├── varchar_valid.sql.snap │ │ │ ├── bpchar.sql.snap │ │ │ ├── alter_table.sql.snap │ │ │ └── basic.sql.snap │ │ │ ├── renamingColumn │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── banDropNotNull │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── addingPrimaryKeyConstraint │ │ │ ├── basic.sql │ │ │ ├── serial_column.sql │ │ │ ├── using_index.sql │ │ │ ├── using_index.sql.snap │ │ │ ├── basic.sql.snap │ │ │ └── serial_column.sql.snap │ │ │ ├── preferBigintOverInt │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── creatingEnum │ │ │ ├── basic.sql │ │ │ ├── simple_enum.sql │ │ │ ├── with_schema.sql │ │ │ ├── valid_create_table.sql │ │ │ ├── valid_create_table.sql.snap │ │ │ ├── valid_lookup_table.sql │ │ │ ├── valid_lookup_table.sql.snap │ │ │ ├── basic.sql.snap │ │ │ ├── simple_enum.sql.snap │ │ │ └── with_schema.sql.snap │ │ │ ├── preferJsonb │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── preferRobustStmts │ │ │ ├── drop_without_if_exists.sql │ │ │ ├── basic.sql │ │ │ ├── robust_statements.sql │ │ │ ├── robust_statements.sql.snap │ │ │ ├── drop_without_if_exists.sql.snap │ │ │ └── basic.sql.snap │ │ │ ├── addingNotNullField │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── preferBigintOverSmallint │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── requireConcurrentIndexDeletion │ │ │ ├── basic.sql │ │ │ ├── concurrent_valid.sql │ │ │ ├── concurrent_valid.sql.snap │ │ │ └── basic.sql.snap │ │ │ ├── addingFieldWithDefault │ │ │ ├── basic.sql │ │ │ ├── volatile_default.sql │ │ │ ├── no_default.sql │ │ │ ├── generated_column.sql │ │ │ ├── non_volatile_default.sql │ │ │ ├── no_default.sql.snap │ │ │ ├── basic.sql.snap │ │ │ ├── volatile_default.sql.snap │ │ │ ├── generated_column.sql.snap │ │ │ └── non_volatile_default.sql.snap │ │ │ ├── requireConcurrentIndexCreation │ │ │ ├── basic.sql │ │ │ ├── concurrent_valid.sql │ │ │ ├── new_table_valid.sql │ │ │ ├── concurrent_valid.sql.snap │ │ │ ├── new_table_valid.sql.snap │ │ │ └── basic.sql.snap │ │ │ ├── changingColumnType │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── addSerialColumn │ │ │ ├── basic.sql │ │ │ ├── valid_regular_column.sql │ │ │ ├── bigserial.sql │ │ │ ├── smallserial.sql │ │ │ ├── valid_generated_virtual.sql │ │ │ ├── generated_stored.sql │ │ │ ├── valid_create_table.sql │ │ │ ├── valid_regular_column.sql.snap │ │ │ ├── valid_generated_virtual.sql.snap │ │ │ ├── valid_create_table.sql.snap │ │ │ ├── basic.sql.snap │ │ │ ├── bigserial.sql.snap │ │ │ ├── smallserial.sql.snap │ │ │ └── generated_stored.sql.snap │ │ │ ├── constraintMissingNotValid │ │ │ ├── check_constraint.sql │ │ │ ├── basic.sql │ │ │ ├── with_not_valid.sql │ │ │ ├── with_not_valid.sql.snap │ │ │ ├── check_constraint.sql.snap │ │ │ └── basic.sql.snap │ │ │ ├── addingForeignKeyConstraint │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ ├── lockTimeoutWarning │ │ │ ├── basic.sql │ │ │ ├── timeout_set_variant.sql │ │ │ ├── valid_concurrent_index.sql │ │ │ ├── valid_with_timeout.sql │ │ │ ├── create_index_without_timeout.sql │ │ │ ├── valid_new_table.sql │ │ │ ├── valid_index_on_new_table.sql │ │ │ ├── multiple_statements_without_timeout.sql │ │ │ ├── timeout_set_variant.sql.snap │ │ │ ├── valid_concurrent_index.sql.snap │ │ │ ├── valid_with_timeout.sql.snap │ │ │ ├── valid_new_table.sql.snap │ │ │ ├── valid_index_on_new_table.sql.snap │ │ │ └── basic.sql.snap │ │ │ ├── multipleAlterTable │ │ │ ├── valid_single_alter.sql │ │ │ ├── valid_combined.sql │ │ │ ├── valid_different_tables.sql │ │ │ ├── basic.sql │ │ │ ├── with_schema.sql │ │ │ ├── mixed_schema_notation.sql │ │ │ ├── valid_different_schemas.sql │ │ │ ├── three_alters.sql │ │ │ ├── valid_single_alter.sql.snap │ │ │ ├── valid_combined.sql.snap │ │ │ ├── valid_different_tables.sql.snap │ │ │ └── valid_different_schemas.sql.snap │ │ │ ├── banConcurrentIndexCreationInTransaction │ │ │ ├── basic.sql │ │ │ └── basic.sql.snap │ │ │ └── runningStatementWhileHoldingAccessExclusive │ │ │ ├── valid_single_alter.sql │ │ │ ├── valid_before_alter.sql │ │ │ ├── basic.sql │ │ │ ├── create_index_after_alter.sql │ │ │ ├── insert_after_alter.sql │ │ │ ├── valid_new_table.sql │ │ │ ├── multiple_statements.sql │ │ │ ├── valid_single_alter.sql.snap │ │ │ ├── valid_before_alter.sql.snap │ │ │ ├── valid_new_table.sql.snap │ │ │ ├── basic.sql.snap │ │ │ ├── create_index_after_alter.sql.snap │ │ │ └── insert_after_alter.sql.snap │ └── src │ │ ├── lint.rs │ │ └── registry.rs ├── pgls_console │ ├── tests │ │ └── markup │ │ │ ├── invalid_group.rs │ │ │ ├── invalid_punct.rs │ │ │ ├── open_element_unfinished_1.rs │ │ │ ├── unclosed_element.rs │ │ │ ├── element_non_ident_name.rs │ │ │ ├── open_element_unfinished_2.rs │ │ │ ├── closing_element_standalone.rs │ │ │ ├── open_element_unfinished_3.rs │ │ │ ├── open_element_unfinished_4.rs │ │ │ ├── open_element_unfinished_5.rs │ │ │ ├── open_element_improper_close_1.rs │ │ │ ├── open_element_unfinished_6.rs │ │ │ ├── open_element_unfinished_7.rs │ │ │ ├── open_element_improper_close_2.rs │ │ │ ├── invalid_punct.stderr │ │ │ ├── open_element_missing_prop_value.rs │ │ │ ├── invalid_group.stderr │ │ │ ├── open_element_improper_prop_value.rs │ │ │ ├── unclosed_element.stderr │ │ │ ├── element_non_ident_name.stderr │ │ │ ├── open_element_improper_close_1.stderr │ │ │ ├── closing_element_standalone.stderr │ │ │ ├── open_element_improper_close_2.stderr │ │ │ ├── open_element_missing_prop_value.stderr │ │ │ ├── open_element_improper_prop_value.stderr │ │ │ ├── open_element_unfinished_1.stderr │ │ │ ├── open_element_unfinished_2.stderr │ │ │ ├── open_element_unfinished_3.stderr │ │ │ ├── open_element_unfinished_4.stderr │ │ │ ├── open_element_unfinished_5.stderr │ │ │ ├── open_element_unfinished_6.stderr │ │ │ └── open_element_unfinished_7.stderr │ ├── README.md │ └── src │ │ └── write.rs ├── pgls_treesitter │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── pgls_type_resolver │ ├── src │ │ ├── lib.rs │ │ └── util.rs │ └── Cargo.toml ├── pgls_workspace │ └── src │ │ ├── features │ │ └── mod.rs │ │ └── workspace │ │ └── server │ │ └── async_helper.rs ├── pgls_lsp │ └── src │ │ ├── handlers.rs │ │ ├── lib.rs │ │ └── documents.rs ├── pgls_text_size │ ├── tests │ │ ├── indexing.rs │ │ ├── constructors.rs │ │ └── auto_traits.rs │ └── Cargo.toml ├── pgls_statement_splitter │ ├── tests │ │ └── data │ │ │ ├── simple_select__4.sql │ │ │ ├── simple_union__4.sql │ │ │ ├── on_conflict_do_update__1.sql │ │ │ ├── with_comments__4.sql │ │ │ └── grant_statements__4.sql │ ├── src │ │ └── splitter │ │ │ └── ddl.rs │ └── Cargo.toml ├── pgls_tokenizer │ ├── README.md │ ├── src │ │ └── snapshots │ │ │ ├── pgls_tokenizer__tests__block_comment.snap │ │ │ ├── pgt_tokenizer__tests__block_comment.snap │ │ │ ├── pgls_tokenizer__tests__block_comment_unterminated.snap │ │ │ ├── pgls_tokenizer__tests__line_comment.snap │ │ │ ├── pgt_tokenizer__tests__block_comment_unterminated.snap │ │ │ ├── pgt_tokenizer__tests__line_comment.snap │ │ │ ├── pgt_tokenizer__tests__lex_statement.snap │ │ │ ├── pgls_tokenizer__tests__lex_statement.snap │ │ │ ├── pgls_tokenizer__tests__quoted_ident_with_escape_quote.snap │ │ │ ├── pgt_tokenizer__tests__quoted_ident_with_escape_quote.snap │ │ │ ├── pgls_tokenizer__tests__dollar_strings_part2.snap │ │ │ ├── pgt_tokenizer__tests__dollar_strings_part2.snap │ │ │ ├── pgls_tokenizer__tests__select_with_period.snap │ │ │ ├── pgt_tokenizer__tests__select_with_period.snap │ │ │ ├── pgls_tokenizer__tests__quoted_ident.snap │ │ │ ├── pgt_tokenizer__tests__quoted_ident.snap │ │ │ ├── pgls_tokenizer__tests__dollar_quote_mismatch_tags_simple.snap │ │ │ ├── pgt_tokenizer__tests__dollar_quote_mismatch_tags_simple.snap │ │ │ ├── pgt_tokenizer__tests__dollar_quote_mismatch_tags_complex.snap │ │ │ ├── pgls_tokenizer__tests__dollar_quote_mismatch_tags_complex.snap │ │ │ ├── pgls_tokenizer__tests__line_comment_whitespace.snap │ │ │ ├── pgt_tokenizer__tests__line_comment_whitespace.snap │ │ │ ├── pgt_tokenizer__tests__named_param_at.snap │ │ │ ├── pgls_tokenizer__tests__named_param_at.snap │ │ │ ├── pgls_tokenizer__tests__named_param_colon_raw.snap │ │ │ ├── pgt_tokenizer__tests__named_param_colon_raw.snap │ │ │ ├── pgt_tokenizer__tests__named_param_dollar_raw.snap │ │ │ ├── pgls_tokenizer__tests__named_param_dollar_raw.snap │ │ │ ├── pgls_tokenizer__tests__named_param_colon_string.snap │ │ │ ├── pgt_tokenizer__tests__named_param_colon_string.snap │ │ │ ├── pgls_tokenizer__tests__named_param_colon_identifier.snap │ │ │ ├── pgt_tokenizer__tests__named_param_colon_identifier.snap │ │ │ ├── pgt_tokenizer__tests__named_param_colon_raw_vs_cast.snap │ │ │ ├── pgls_tokenizer__tests__named_param_colon_raw_vs_cast.snap │ │ │ ├── pgls_tokenizer__tests__bitstring.snap │ │ │ ├── pgt_tokenizer__tests__bitstring.snap │ │ │ ├── pgt_tokenizer__tests__graphile_named_param.snap │ │ │ ├── pgls_tokenizer__tests__graphile_named_param.snap │ │ │ ├── pgt_tokenizer__tests__params.snap │ │ │ ├── pgls_tokenizer__tests__params.snap │ │ │ ├── pgls_tokenizer__tests__dollar_quoting.snap │ │ │ └── pgt_tokenizer__tests__dollar_quoting.snap │ └── Cargo.toml ├── pgls_lexer_codegen │ ├── README.md │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── pgls_typecheck │ └── tests │ │ └── snapshots │ │ ├── invalid_column.snap │ │ ├── invalid_type_in_function_longer_default.snap │ │ ├── operator_does_not_exist.snap │ │ ├── invalid_type_in_function_shorter_default.snap │ │ ├── testing_type_with_multiple_words.snap │ │ └── testing_operator_type_with_multiple_words.snap ├── pgls_completions │ └── src │ │ ├── lib.rs │ │ ├── providers │ │ └── mod.rs │ │ └── relevance.rs ├── pgls_hover │ └── tests │ │ └── snapshots │ │ ├── no_hover_keyword.snap │ │ ├── at-param.snap │ │ ├── colon-param.snap │ │ ├── dollar-param.snap │ │ ├── questionmark-param.snap │ │ ├── role_create.snap │ │ ├── builtin_now.snap │ │ ├── column_hover_quoted_column_name.snap │ │ ├── table_hover.snap │ │ ├── grant_select.snap │ │ ├── hover_on_schemas.snap │ │ ├── role_alter.snap │ │ ├── revoke_select.snap │ │ ├── table_hover_quoted_schema.snap │ │ ├── table_hover_quoted_table_name.snap │ │ ├── column_hover.snap │ │ ├── hover_type_in_select_clause.snap │ │ ├── create_policy.snap │ │ ├── column_hover_quoted_schema_table.snap │ │ ├── column_hover_join.snap │ │ ├── column_hover_quoted_column_name_with_table.snap │ │ ├── hover_custom_type_enum.snap │ │ ├── hover_custom_type_with_properties.snap │ │ ├── lenghty_function.snap │ │ ├── builtin_count.snap │ │ ├── function_with_schema.snap │ │ ├── custom_function.snap │ │ └── function_hover_quoted_schema.snap ├── pgls_schema_cache │ └── src │ │ ├── queries │ │ ├── policies.sql │ │ ├── extensions.sql │ │ ├── versions.sql │ │ └── triggers.sql │ │ ├── extensions.rs │ │ ├── lib.rs │ │ └── schemas.rs ├── pgls_treesitter_grammar │ ├── .gitattributes │ ├── .editorconfig │ ├── .gitignore │ └── tests │ │ └── snapshots │ │ ├── grammar_tests__test_4.snap │ │ └── grammar_tests__test_1.snap ├── pgls_configuration │ └── src │ │ ├── rules │ │ └── mod.rs │ │ ├── migrations.rs │ │ └── plpgsql_check.rs ├── pgls_fs │ └── src │ │ └── lib.rs ├── pgls_markup │ ├── README.md │ └── Cargo.toml ├── pgls_env │ └── Cargo.toml ├── pgls_workspace_macros │ └── Cargo.toml ├── pgls_test_macros │ └── Cargo.toml ├── pgls_suppressions │ └── Cargo.toml ├── pgls_diagnostics_macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── pgls_diagnostics_categories │ └── Cargo.toml ├── pgls_query_macros │ └── Cargo.toml ├── pgls_test_utils │ └── Cargo.toml └── pgls_text_edit │ └── Cargo.toml ├── editors └── code │ ├── .eslintignore │ ├── .prettierignore │ ├── README.md │ ├── .gitignore │ ├── .prettierrc.js │ ├── tsconfig.eslint.json │ ├── .vscodeignore │ └── tsconfig.json ├── packages ├── @postgres-language-server │ └── cli │ │ └── test │ │ └── test.sql └── @postgrestools │ └── postgrestools │ └── test │ └── test.sql ├── rustfmt.toml ├── rust-toolchain.toml ├── .env ├── .vscode └── settings.json ├── docs ├── images │ ├── cli-demo.png │ ├── lsp-demo.gif │ └── pls-github.png ├── codegen │ └── src │ │ ├── lib.rs │ │ ├── version.rs │ │ ├── cli_doc.rs │ │ └── default_configuration.rs └── reference │ └── rules │ └── ban-drop-database.md ├── xtask ├── rules_check │ ├── src │ │ └── main.rs │ └── Cargo.toml └── Cargo.toml ├── example └── file.sql ├── .github ├── ISSUE_TEMPLATE │ ├── 4.Planned_work.md │ ├── 3.Create_a_chore.md │ ├── config.yml │ └── 2.Improve_docs.md └── actions │ └── free-disk-space │ └── action.yml ├── test-db └── seed.sql ├── .gitmodules ├── .gitattributes ├── taplo.toml ├── docker-compose.yml ├── .claude └── settings.local.json ├── pyproject.toml ├── Dockerfile ├── biome.jsonc ├── package.json ├── postgrestools.jsonc ├── test.sql ├── tsconfig.json └── .gitignore /clippy.toml: -------------------------------------------------------------------------------- 1 | allow-dbg-in-tests = true 2 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | @./AGENTS.md 4 | 5 | -------------------------------------------------------------------------------- /crates/pgls_lexer/src/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod syntax_kind; 2 | -------------------------------------------------------------------------------- /editors/code/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .eslintrc.js 3 | -------------------------------------------------------------------------------- /packages/@postgres-language-server/cli/test/test.sql: -------------------------------------------------------------------------------- 1 | select 1; -------------------------------------------------------------------------------- /packages/@postgrestools/postgrestools/test/test.sql: -------------------------------------------------------------------------------- 1 | select 1; -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Unix" 2 | edition = "2024" 3 | -------------------------------------------------------------------------------- /editors/code/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode-test 3 | out 4 | -------------------------------------------------------------------------------- /crates/pgls_query/src/iter_mut.rs: -------------------------------------------------------------------------------- 1 | pgls_query_macros::iter_mut_codegen!(); 2 | -------------------------------------------------------------------------------- /crates/pgls_query/src/iter_ref.rs: -------------------------------------------------------------------------------- 1 | pgls_query_macros::iter_ref_codegen!(); 2 | -------------------------------------------------------------------------------- /crates/pgls_query_ext/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod diagnostics; 2 | pub mod utils; 3 | -------------------------------------------------------------------------------- /crates/pgls_cli/tests/fixtures/test.sql: -------------------------------------------------------------------------------- 1 | alter tqjable test drop column id; 2 | 3 | -------------------------------------------------------------------------------- /crates/pgls_splinter/vendor/COMMIT_SHA.txt: -------------------------------------------------------------------------------- 1 | 27ea2ece65464213e466cd969cc61b6940d16219 -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | profile = "default" 3 | channel = "1.88.0" 4 | -------------------------------------------------------------------------------- /crates/pgls_cli/tests/fixtures/traversal/bad.sql: -------------------------------------------------------------------------------- 1 | alter tqjable bad drop column id; 2 | -------------------------------------------------------------------------------- /crates/pgls_lexer/src/codegen/syntax_kind.rs: -------------------------------------------------------------------------------- 1 | pgls_lexer_codegen::syntax_kind_codegen!(); 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SQLX_OFFLINE=true 2 | DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/postgres 3 | -------------------------------------------------------------------------------- /crates/pgls_cli/tests/fixtures/traversal/another_bad.sql: -------------------------------------------------------------------------------- 1 | alter tqjable another drop column id; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "postgres-language-server.bin": "./target/debug/postgres-language-server" 3 | } 4 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banDropTable/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/banDropTable 2 | drop table test; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/transactionNesting/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/transactionNesting 2 | BEGIN; -------------------------------------------------------------------------------- /editors/code/README.md: -------------------------------------------------------------------------------- 1 | # postgres_lsp for VSCode 2 | 3 | This extension provides VSCode support for `postgres_lsp`. 4 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/invalid_group.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | [] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/invalid_punct.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | ! 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_treesitter/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod context; 2 | pub mod queries; 3 | 4 | pub use context::*; 5 | pub use queries::*; 6 | -------------------------------------------------------------------------------- /docs/images/cli-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/postgres-language-server/HEAD/docs/images/cli-demo.png -------------------------------------------------------------------------------- /docs/images/lsp-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/postgres-language-server/HEAD/docs/images/lsp-demo.gif -------------------------------------------------------------------------------- /editors/code/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | server 4 | .vscode-test/ 5 | *.vsix 6 | bundle 7 | vscode.proposed.d.ts 8 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banDropDatabase/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/banDropDatabase 2 | drop database all_users; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banTruncateCascade/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/banTruncateCascade 2 | truncate a cascade; -------------------------------------------------------------------------------- /crates/pgls_type_resolver/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod functions; 2 | mod types; 3 | mod util; 4 | 5 | pub use functions::resolve_func_call; 6 | -------------------------------------------------------------------------------- /docs/images/pls-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/postgres-language-server/HEAD/docs/images/pls-github.png -------------------------------------------------------------------------------- /xtask/rules_check/src/main.rs: -------------------------------------------------------------------------------- 1 | use rules_check::check_rules; 2 | 3 | fn main() -> anyhow::Result<()> { 4 | check_rules() 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banDropColumn/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/banDropColumn 2 | alter table test 3 | drop column id; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/renamingTable/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/renamingTable 2 | ALTER TABLE users RENAME TO customers; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/transactionNesting/commit.sql: -------------------------------------------------------------------------------- 1 | SELECT 1; 2 | -- expect_lint/safety/transactionNesting 3 | COMMIT; 4 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_unfinished_1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | < 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/unclosed_element.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_workspace/src/features/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod code_actions; 2 | pub mod completions; 3 | pub mod diagnostics; 4 | pub mod on_hover; 5 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingRequiredField/without_required.sql: -------------------------------------------------------------------------------- 1 | -- expect_no_diagnostics 2 | alter table test 3 | add column c int; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/transactionNesting/rollback.sql: -------------------------------------------------------------------------------- 1 | SELECT 1; 2 | -- expect_lint/safety/transactionNesting 3 | ROLLBACK; 4 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/element_non_ident_name.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | <"Literal" /> 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_unfinished_2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_unfinished_3.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | tests/markup/invalid_punct.rs:3:9 3 | | 4 | 3 | ! 5 | | ^ 6 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_missing_prop_value.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_only_lint/safety/creatingEnum 2 | CREATE TYPE document_type AS ENUM ('invoice', 'receipt', 'other'); -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferJsonb/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/preferJsonb 2 | CREATE TABLE users ( 3 | id integer, 4 | data json 5 | ); 6 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferRobustStmts/drop_without_if_exists.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/preferRobustStmts 2 | DROP INDEX CONCURRENTLY users_email_idx; 3 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/invalid_group.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected token 2 | --> tests/markup/invalid_group.rs:3:9 3 | | 4 | 3 | [] 5 | | ^^ 6 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_improper_prop_value.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pgls_console::markup! { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingNotNullField/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/addingNotNullField 2 | ALTER TABLE "core_recipe" ALTER COLUMN "foo" SET NOT NULL; 3 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferBigintOverSmallint/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/preferBigintOverSmallint 2 | CREATE TABLE users ( 3 | age smallint 4 | ); 5 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexDeletion/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/requireConcurrentIndexDeletion 2 | DROP INDEX IF EXISTS users_email_idx; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/addingFieldWithDefault 2 | ALTER TABLE "core_recipe" ADD COLUMN "foo" integer DEFAULT 10; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexCreation/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/requireConcurrentIndexCreation 2 | CREATE INDEX users_email_idx ON users (email); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/4.Planned_work.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Planned work 3 | about: Planned feature work. Limited to contributors only. 4 | labels: planned 5 | --- 6 | 7 | # Planned Work 8 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingPrimaryKeyConstraint/serial_column.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/addingPrimaryKeyConstraint 2 | ALTER TABLE items ADD COLUMN id SERIAL PRIMARY KEY; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferRobustStmts/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/preferRobustStmts 2 | CREATE INDEX CONCURRENTLY users_email_idx ON users (email); 3 | SELECT 1; -------------------------------------------------------------------------------- /crates/pgls_splinter/tests/snapshots/no_issues.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_splinter/tests/diagnostics.rs 3 | expression: content 4 | snapshot_kind: text 5 | --- 6 | No Diagnostics 7 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/changingColumnType/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/changingColumnType 2 | ALTER TABLE "core_recipe" ALTER COLUMN "edits" TYPE text USING "edits"::text; -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/unclosed_element.stderr: -------------------------------------------------------------------------------- 1 | error: unclosed element 2 | --> tests/markup/unclosed_element.rs:3:10 3 | | 4 | 3 | 5 | | ^^^^^^^^ 6 | -------------------------------------------------------------------------------- /test-db/seed.sql: -------------------------------------------------------------------------------- 1 | create table public.contact ( 2 | id serial primary key not null, 3 | created_at timestamp with time zone not null default now(), 4 | username text 5 | ); 6 | 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "crates/pgls_query/vendor/libpg_query"] 2 | path = crates/pgls_query/vendor/libpg_query 3 | url = https://github.com/pganalyze/libpg_query.git 4 | branch = 17-latest 5 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferIdentity/valid.sql: -------------------------------------------------------------------------------- 1 | -- expect_no_diagnostics 2 | create table users_valid ( 3 | id bigint generated by default as identity primary key 4 | ); 5 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/addSerialColumn 2 | -- Test adding serial column to existing table 3 | ALTER TABLE prices ADD COLUMN id serial; 4 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/volatile_default.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/addingFieldWithDefault 2 | ALTER TABLE users ADD COLUMN created_at timestamp DEFAULT now(); 3 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/constraintMissingNotValid/check_constraint.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/constraintMissingNotValid 2 | ALTER TABLE users ADD CONSTRAINT check_age CHECK (age >= 0); 3 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/simple_enum.sql: -------------------------------------------------------------------------------- 1 | -- expect_only_lint/safety/creatingEnum 2 | -- Simple enum with two values 3 | CREATE TYPE status AS ENUM ('active', 'inactive'); 4 | -------------------------------------------------------------------------------- /crates/pgls_splinter/tests/snapshots/rls_disabled_in_public.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_splinter/tests/diagnostics.rs 3 | expression: content 4 | snapshot_kind: text 5 | --- 6 | No Diagnostics 7 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/valid_regular_column.sql: -------------------------------------------------------------------------------- 1 | -- Test adding regular column (should be safe) 2 | -- expect_no_diagnostics 3 | ALTER TABLE prices ADD COLUMN name text; 4 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/no_default.sql: -------------------------------------------------------------------------------- 1 | -- Test adding column without default (should be safe) 2 | -- expect_no_diagnostics 3 | ALTER TABLE users ADD COLUMN email text; -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/element_non_ident_name.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected token 2 | --> tests/markup/element_non_ident_name.rs:3:10 3 | | 4 | 3 | <"Literal" /> 5 | | ^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /crates/pgls_text_size/tests/indexing.rs: -------------------------------------------------------------------------------- 1 | use pgls_text_size::*; 2 | 3 | #[test] 4 | fn main() { 5 | let range = TextRange::default(); 6 | _ = &""[range]; 7 | _ = &String::new()[range]; 8 | } 9 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banCharField/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/banCharField 2 | CREATE TABLE "core_bar" ( 3 | "id" serial NOT NULL PRIMARY KEY, 4 | "alpha" char(100) NOT NULL 5 | ); -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/transactionNesting/begin_commit_combined.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/transactionNesting 2 | BEGIN; 3 | SELECT 1; 4 | -- expect_lint/safety/transactionNesting 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/bigserial.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/addSerialColumn 2 | -- Test adding bigserial column to existing table 3 | ALTER TABLE prices ADD COLUMN big_id bigserial; 4 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banCharField/varchar_valid.sql: -------------------------------------------------------------------------------- 1 | -- expect_no_diagnostics 2 | CREATE TABLE "core_bar" ( 3 | "id" serial NOT NULL PRIMARY KEY, 4 | "alpha" varchar(100) NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_improper_close_1.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected token 2 | --> tests/markup/open_element_improper_close_1.rs:3:20 3 | | 4 | 3 | tests/markup/closing_element_standalone.rs:3:11 3 | | 4 | 3 | 5 | | ^^^^^^^^ 6 | -------------------------------------------------------------------------------- /crates/pgls_statement_splitter/tests/data/simple_select__4.sql: -------------------------------------------------------------------------------- 1 | select id, name, test1231234123, unknown from co; 2 | 3 | select 14433313331333 4 | 5 | alter table test drop column id; 6 | 7 | select lower('test'); 8 | 9 | -------------------------------------------------------------------------------- /crates/pgls_statement_splitter/tests/data/simple_union__4.sql: -------------------------------------------------------------------------------- 1 | select 1 union all select 2; 2 | 3 | select 1 union select 2; 4 | 5 | select 1 union select 2 except select 3; 6 | 7 | select 1 union all select 2 except select 3; -------------------------------------------------------------------------------- /crates/pgls_analyser/src/lint.rs: -------------------------------------------------------------------------------- 1 | //! Generated file, do not edit by hand, see `xtask/codegen` 2 | 3 | pub mod safety; 4 | ::pgls_analyse::declare_category! { pub Lint { kind : Lint , groups : [self :: safety :: Safety ,] } } 5 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/with_schema.sql: -------------------------------------------------------------------------------- 1 | -- expect_only_lint/safety/creatingEnum 2 | -- Enum type with schema qualification 3 | CREATE TYPE myschema.status AS ENUM ('active', 'inactive', 'pending'); 4 | -------------------------------------------------------------------------------- /crates/pgls_statement_splitter/tests/data/on_conflict_do_update__1.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO foo.bar ( 2 | pk 3 | ) VALUES ( 4 | $1 5 | ) ON CONFLICT (pk) DO UPDATE SET 6 | date_deleted = DEFAULT, 7 | date_created = DEFAULT; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexCreation/concurrent_valid.sql: -------------------------------------------------------------------------------- 1 | -- Test concurrent index creation (should be safe) 2 | -- expect_no_diagnostics 3 | CREATE INDEX CONCURRENTLY users_email_idx ON users (email); -------------------------------------------------------------------------------- /crates/pgls_lexer/README.md: -------------------------------------------------------------------------------- 1 | Heavily inspired by and copied from [squawk_parser](https://github.com/sbdchd/squawk/tree/9acfecbbb7f3c7eedcbaf060e7b25f9afa136db3/crates/squawk_parser). Thanks for making all the hard work MIT-licensed! 2 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/README.md: -------------------------------------------------------------------------------- 1 | Heavily inspired by and copied from [squawk_lexer](https://github.com/sbdchd/squawk/tree/9acfecbbb7f3c7eedcbaf060e7b25f9afa136db3/crates/squawk_lexer). Thanks for making all the hard work MIT-licensed! 2 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/valid_generated_virtual.sql: -------------------------------------------------------------------------------- 1 | -- Test adding regular column with integer type (should be safe) 2 | -- expect_no_diagnostics 3 | ALTER TABLE prices ADD COLUMN quantity integer DEFAULT 0; 4 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/generated_column.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/addingFieldWithDefault 2 | ALTER TABLE users ADD COLUMN full_name text GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED; 3 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banConcurrentIndexCreationInTransaction/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/banConcurrentIndexCreationInTransaction 2 | CREATE INDEX CONCURRENTLY "field_name_idx" ON "table_name" ("field_name"); 3 | SELECT 1; -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_improper_close_2.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected token 2 | --> tests/markup/open_element_improper_close_2.rs:3:20 3 | | 4 | 3 | tests/markup/open_element_missing_prop_value.rs:3:28 3 | | 4 | 3 | 5 | | ^ 6 | -------------------------------------------------------------------------------- /crates/pgls_typecheck/tests/snapshots/invalid_column.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_typecheck/tests/diagnostics.rs 3 | expression: content 4 | --- 5 | select id, ~~~unknown~~~ from contacts; 6 | 7 | column "unknown" does not exist 8 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingPrimaryKeyConstraint/using_index.sql: -------------------------------------------------------------------------------- 1 | -- expect_no_diagnostics 2 | -- This should not trigger the rule - using an existing index 3 | ALTER TABLE items ADD CONSTRAINT items_pk PRIMARY KEY USING INDEX items_pk; -------------------------------------------------------------------------------- /taplo.toml: -------------------------------------------------------------------------------- 1 | include = ["Cargo.toml", "crates/**/Cargo.toml", ".cargo/config.toml", "xtask/**/*.toml", "knope.toml"] 2 | exclude = ["./benchmark/**/*.toml"] 3 | 4 | [formatting] 5 | align_entries = true 6 | column_width = 120 7 | reorder_keys = true 8 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/valid_single_alter.sql: -------------------------------------------------------------------------------- 1 | -- Valid: ALTER TABLE alone in transaction (no subsequent statements) 2 | -- expect_no_diagnostics 3 | ALTER TABLE authors ADD COLUMN email TEXT; 4 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_improper_prop_value.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected token 2 | --> tests/markup/open_element_improper_prop_value.rs:3:28 3 | | 4 | 3 | 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /docs/codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cli_doc; 2 | pub mod default_configuration; 3 | pub mod env_variables; 4 | pub mod rules_docs; 5 | pub mod rules_index; 6 | pub mod rules_sources; 7 | pub mod schema; 8 | pub mod version; 9 | 10 | mod utils; 11 | -------------------------------------------------------------------------------- /crates/pgls_lsp/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod adapters; 2 | mod capabilities; 3 | mod diagnostics; 4 | mod documents; 5 | mod handlers; 6 | mod server; 7 | mod session; 8 | mod utils; 9 | 10 | pub use crate::server::{LSPServer, ServerConnection, ServerFactory}; 11 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/create_index_without_timeout.sql: -------------------------------------------------------------------------------- 1 | -- expect_only_lint/safety/lockTimeoutWarning 2 | -- CREATE INDEX without CONCURRENTLY or lock timeout should trigger the rule 3 | CREATE INDEX books_title_idx ON books(title); 4 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexCreation/new_table_valid.sql: -------------------------------------------------------------------------------- 1 | -- Test index on newly created table (should be safe) 2 | -- expect_no_diagnostics 3 | CREATE TABLE users (id serial, email text); 4 | CREATE INDEX users_email_idx ON users (email); -------------------------------------------------------------------------------- /crates/pgls_type_resolver/src/util.rs: -------------------------------------------------------------------------------- 1 | pub(crate) fn get_string_from_node(node: &pgls_query::protobuf::Node) -> String { 2 | match &node.node { 3 | Some(pgls_query::NodeEnum::String(s)) => s.sval.to_string(), 4 | _ => "".to_string(), 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /crates/pgls_analyser/src/registry.rs: -------------------------------------------------------------------------------- 1 | //! Generated file, do not edit by hand, see `xtask/codegen` 2 | 3 | use pgls_analyse::RegistryVisitor; 4 | pub fn visit_registry(registry: &mut V) { 5 | registry.record_category::(); 6 | } 7 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/constraintMissingNotValid/with_not_valid.sql: -------------------------------------------------------------------------------- 1 | -- Test constraint with NOT VALID (should be safe) 2 | -- expect_no_diagnostics 3 | ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses (address) NOT VALID; -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/valid_combined.sql: -------------------------------------------------------------------------------- 1 | -- Test single ALTER TABLE with multiple actions (should be safe) 2 | -- expect_no_diagnostics 3 | ALTER TABLE authors 4 | ALTER COLUMN name SET NOT NULL, 5 | ALTER COLUMN email SET NOT NULL; 6 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/valid_different_tables.sql: -------------------------------------------------------------------------------- 1 | -- Test ALTER TABLE on different tables (should be safe) 2 | -- expect_no_diagnostics 3 | ALTER TABLE authors ADD COLUMN bio text; 4 | ALTER TABLE posts ADD COLUMN published_at timestamp; 5 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/valid_before_alter.sql: -------------------------------------------------------------------------------- 1 | -- Valid: Statements before ALTER TABLE are fine 2 | -- expect_no_diagnostics 3 | SELECT COUNT(*) FROM authors; 4 | CREATE INDEX authors_name_idx ON authors(name); 5 | -------------------------------------------------------------------------------- /crates/pgls_typecheck/tests/snapshots/invalid_type_in_function_longer_default.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_typecheck/tests/diagnostics.rs 3 | expression: content 4 | --- 5 | delete from public.contacts where id = ~~~uid~~~; 6 | 7 | `uid` is of type uuid, not integer 8 | -------------------------------------------------------------------------------- /crates/pgls_typecheck/tests/snapshots/operator_does_not_exist.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_typecheck/tests/diagnostics.rs 3 | expression: content 4 | --- 5 | select ~~~is_active + product_name~~~ from public.products; 6 | 7 | operator does not exist: boolean + unknown 8 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferRobustStmts/robust_statements.sql: -------------------------------------------------------------------------------- 1 | -- Test proper robust statements (should be safe) 2 | -- expect_no_diagnostics 3 | CREATE INDEX CONCURRENTLY IF NOT EXISTS users_email_idx ON users (email); 4 | DROP INDEX CONCURRENTLY IF EXISTS old_idx; -------------------------------------------------------------------------------- /crates/pgls_completions/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod complete; 3 | mod item; 4 | mod providers; 5 | mod relevance; 6 | mod sanitization; 7 | 8 | #[cfg(test)] 9 | mod test_helper; 10 | 11 | pub use complete::*; 12 | pub use item::*; 13 | pub use sanitization::*; 14 | -------------------------------------------------------------------------------- /crates/pgls_lexer_codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod keywords; 2 | mod syntax_kind; 3 | 4 | use syntax_kind::syntax_kind_mod; 5 | 6 | #[proc_macro] 7 | pub fn syntax_kind_codegen(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { 8 | syntax_kind_mod().into() 9 | } 10 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/generated_stored.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/addSerialColumn 2 | -- Test adding GENERATED ALWAYS AS ... STORED column to existing table 3 | ALTER TABLE prices ADD COLUMN total integer GENERATED ALWAYS AS (price * quantity) STORED; 4 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/multipleAlterTable 2 | -- Test multiple ALTER TABLE statements on the same table 3 | ALTER TABLE authors ALTER COLUMN name SET NOT NULL; 4 | ALTER TABLE authors ALTER COLUMN email SET NOT NULL; 5 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingRequiredField/without_required.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ``` 7 | -- expect_no_diagnostics 8 | alter table test 9 | add column c int; 10 | ``` 11 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/with_schema.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/multipleAlterTable 2 | -- Test multiple ALTER TABLE statements with explicit schema 3 | ALTER TABLE public.users ADD COLUMN age integer; 4 | ALTER TABLE public.users ADD COLUMN country text; 5 | -------------------------------------------------------------------------------- /crates/pgls_typecheck/tests/snapshots/invalid_type_in_function_shorter_default.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_typecheck/tests/diagnostics.rs 3 | expression: content 4 | --- 5 | delete from public.contacts where id = ~~~contact_name~~~; 6 | 7 | `contact_name` is of type text, not integer 8 | -------------------------------------------------------------------------------- /crates/pgls_typecheck/tests/snapshots/testing_type_with_multiple_words.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_typecheck/tests/diagnostics.rs 3 | expression: content 4 | --- 5 | select * from public.products where released = ~~~pid~~~; 6 | 7 | `pid` is of type uuid, not timestamp with time zone 8 | -------------------------------------------------------------------------------- /editors/code/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // use 100 because it's Rustfmt's default 3 | // https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#max_width 4 | printWidth: 100, 5 | singleQuote: true, 6 | tabWidth: 4, 7 | trailingComma: 'none' 8 | }; 9 | -------------------------------------------------------------------------------- /crates/pgls_cli/tests/fixtures/traversal/postgres-language-server.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://pg-language-server.com/schema/postgres-language-server.schema.json", 3 | "linter": { 4 | "enabled": true, 5 | "rules": { 6 | "recommended": true 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/mixed_schema_notation.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/multipleAlterTable 2 | -- Test mixing implicit and explicit public schema (should match) 3 | ALTER TABLE authors ADD COLUMN name text; 4 | ALTER TABLE public.authors ADD COLUMN email text; 5 | -------------------------------------------------------------------------------- /crates/pgls_console/README.md: -------------------------------------------------------------------------------- 1 | # `pgls_console` 2 | 3 | The crate contains a general abstraction over printing messages (formatted with markup) and diagnostics to a console. 4 | 5 | ## Acknowledgement 6 | 7 | This crate was initially forked from [biome](https://github.com/biomejs/biome). 8 | 9 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingRequiredField/with_default.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ``` 7 | -- expect_no_diagnostics 8 | alter table test 9 | add column c int not null default 0; 10 | ``` 11 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/valid_different_schemas.sql: -------------------------------------------------------------------------------- 1 | -- Test ALTER TABLE on tables with same name but different schemas (should be safe) 2 | -- expect_no_diagnostics 3 | ALTER TABLE public.users ADD COLUMN age integer; 4 | ALTER TABLE admin.users ADD COLUMN age integer; 5 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/no_hover_keyword.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select id from users 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | No hover information found. 13 | -------------------------------------------------------------------------------- /crates/pgls_typecheck/tests/snapshots/testing_operator_type_with_multiple_words.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_typecheck/tests/diagnostics.rs 3 | expression: content 4 | --- 5 | delete from public.products where ~~~released > pid~~~; 6 | 7 | operator does not exist: timestamp with time zone > integer 8 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/valid_create_table.sql: -------------------------------------------------------------------------------- 1 | -- Test CREATE TABLE with serial column (should be safe, rule only applies to ALTER TABLE) 2 | -- expect_no_diagnostics 3 | CREATE TABLE products ( 4 | id serial PRIMARY KEY, 5 | name text NOT NULL, 6 | price numeric 7 | ); 8 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/non_volatile_default.sql: -------------------------------------------------------------------------------- 1 | -- Test non-volatile default values (should be safe in PG 11+, but we are passing no PG version info in the tests) 2 | -- expect_lint/safety/addingFieldWithDefault 3 | ALTER TABLE users ADD COLUMN status text DEFAULT 'active'; 4 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/basic.sql: -------------------------------------------------------------------------------- 1 | -- expect_only_lint/safety/runningStatementWhileHoldingAccessExclusive 2 | -- Running SELECT after ALTER TABLE should trigger the rule 3 | ALTER TABLE authors ADD COLUMN email TEXT NOT NULL; 4 | SELECT COUNT(*) FROM authors; -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__block_comment.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "/*\n * foo\n * bar\n*/" @ BlockComment { terminated: true }, 9 | ] 10 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__block_comment.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "/*\n * foo\n * bar\n*/" @ BlockComment { terminated: true }, 9 | ] 10 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/three_alters.sql: -------------------------------------------------------------------------------- 1 | -- expect_lint/safety/multipleAlterTable 2 | -- Test ALTER TABLE after other statements 3 | CREATE TABLE products (id serial PRIMARY KEY); 4 | ALTER TABLE products ADD COLUMN description text; 5 | ALTER TABLE products ADD COLUMN price numeric; 6 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/valid_new_table.sql: -------------------------------------------------------------------------------- 1 | -- Valid: ALTER TABLE on a table created in the same transaction doesn't need timeout 2 | -- expect_no_diagnostics 3 | CREATE TABLE users ( 4 | id INT PRIMARY KEY, 5 | name TEXT 6 | ); 7 | 8 | ALTER TABLE users ADD COLUMN email TEXT; 9 | -------------------------------------------------------------------------------- /crates/pgls_completions/src/providers/mod.rs: -------------------------------------------------------------------------------- 1 | mod columns; 2 | mod functions; 3 | mod helper; 4 | mod policies; 5 | mod roles; 6 | mod schemas; 7 | mod tables; 8 | 9 | pub use columns::*; 10 | pub use functions::*; 11 | pub use policies::*; 12 | pub use roles::*; 13 | pub use schemas::*; 14 | pub use tables::*; 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | # postgres://postgres:postgres@127.0.0.1:5432/postgres 4 | build: . 5 | restart: always 6 | environment: 7 | - POSTGRES_USER=postgres 8 | - POSTGRES_PASSWORD=postgres 9 | - POSTGRES_DB=postgres 10 | ports: 11 | - "5432:5432" 12 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__block_comment_unterminated.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "/*\n * foo\n * bar\n /*\n*/" @ BlockComment { terminated: false }, 9 | ] 10 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__line_comment.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "-- foooooooooooo bar buzz" @ LineComment, 9 | "\n" @ LineEnding { count: 1 }, 10 | ] 11 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__block_comment_unterminated.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "/*\n * foo\n * bar\n /*\n*/" @ BlockComment { terminated: false }, 9 | ] 10 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__line_comment.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "-- foooooooooooo bar buzz" @ LineComment, 9 | "\n" @ LineEnding { count: 1 }, 10 | ] 11 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/valid_index_on_new_table.sql: -------------------------------------------------------------------------------- 1 | -- Valid: CREATE INDEX on a table created in the same transaction doesn't need timeout 2 | -- expect_no_diagnostics 3 | CREATE TABLE products ( 4 | id INT PRIMARY KEY, 5 | name TEXT 6 | ); 7 | 8 | CREATE INDEX products_name_idx ON products(name); 9 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/create_index_after_alter.sql: -------------------------------------------------------------------------------- 1 | -- expect_only_lint/safety/runningStatementWhileHoldingAccessExclusive 2 | -- CREATE INDEX after ALTER TABLE should trigger 3 | ALTER TABLE orders ADD COLUMN total DECIMAL(10, 2); 4 | CREATE INDEX orders_total_idx ON orders(total); 5 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__lex_statement.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | ";" @ Semi, 11 | ] 12 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/multiple_statements_without_timeout.sql: -------------------------------------------------------------------------------- 1 | -- Both statements should trigger the rule 2 | -- expect_lint/safety/lockTimeoutWarning 3 | CREATE INDEX orders_user_idx ON orders(user_id); 4 | 5 | -- expect_lint/safety/lockTimeoutWarning 6 | ALTER TABLE orders ADD COLUMN total DECIMAL(10, 2); 7 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/at-param.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select * from users where name = @name; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | No hover information found. 13 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/colon-param.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select * from users where name = :name; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | No hover information found. 13 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/dollar-param.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select * from users where name = $name; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | No hover information found. 13 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__lex_statement.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | ";" @ Semi, 11 | ] 12 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/insert_after_alter.sql: -------------------------------------------------------------------------------- 1 | -- expect_only_lint/safety/runningStatementWhileHoldingAccessExclusive 2 | -- INSERT after ALTER TABLE should trigger 3 | ALTER TABLE books ADD COLUMN isbn TEXT; 4 | INSERT INTO books (title, isbn) VALUES ('Database Systems', '978-0-1234567-8-9'); 5 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/questionmark-param.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select * from users where name = ?name; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | No hover information found. 13 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/role_create.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | create role alternate_owner with superuser createdb login 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | No hover information found. 13 | -------------------------------------------------------------------------------- /crates/pgls_schema_cache/src/queries/policies.sql: -------------------------------------------------------------------------------- 1 | select 2 | schemaname as "schema_name!", 3 | tablename as "table_name!", 4 | policyname as "name!", 5 | permissive as "is_permissive!", 6 | roles as "role_names!", 7 | cmd as "command!", 8 | qual as "security_qualification", 9 | with_check 10 | from 11 | pg_catalog.pg_policies; -------------------------------------------------------------------------------- /crates/pgls_statement_splitter/tests/data/with_comments__4.sql: -------------------------------------------------------------------------------- 1 | -- test 2 | select id, name, test1231234123, unknown from co; 3 | 4 | -- in between two statements 5 | 6 | select 14433313331333 -- after a statement 7 | 8 | alter table --within a statement 9 | test drop column id; 10 | 11 | select lower('test'); 12 | --after a statement 13 | 14 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferIdentity/valid.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_no_diagnostics 9 | create table users_valid ( 10 | id bigint generated by default as identity primary key 11 | ); 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /editors/code/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | // Special typescript project file, used by eslint only. 2 | { 3 | "extends": "./tsconfig.json", 4 | "include": [ 5 | // repeated from base config's "include" setting 6 | "src", 7 | "tests", 8 | // these are the eslint-only inclusions 9 | ".eslintrc.js" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/no_default.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test adding column without default (should be safe) 9 | -- expect_no_diagnostics 10 | ALTER TABLE users ADD COLUMN email text; 11 | ``` 12 | -------------------------------------------------------------------------------- /crates/pgls_treesitter_grammar/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | # Generated source files 4 | src/*.json linguist-generated 5 | src/parser.c linguist-generated 6 | src/tree_sitter/* linguist-generated 7 | 8 | 9 | # Rust bindings 10 | bindings/rust/* linguist-generated 11 | Cargo.toml linguist-generated 12 | Cargo.lock linguist-generated 13 | 14 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/valid_regular_column.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test adding regular column (should be safe) 9 | -- expect_no_diagnostics 10 | ALTER TABLE prices ADD COLUMN name text; 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /.claude/settings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "allow": [ 4 | "Bash(grep:*)", 5 | "Bash(rg:*)", 6 | "Bash(cargo test:*)", 7 | "Bash(cargo run:*)", 8 | "Bash(cargo check:*)", 9 | "Bash(cargo fmt:*)", 10 | "Bash(cargo doc:*)", 11 | "Bash(just tree-print:*)" 12 | ], 13 | "deny": [] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3.Create_a_chore.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Create a chore 3 | about: Changes to build processes, tools, refactors. 4 | labels: chore 5 | --- 6 | 7 | # Chore 8 | 9 | ## Describe the chore 10 | 11 | A clear and concise description of what the chore is. 12 | 13 | ## Additional context 14 | 15 | Add any other context or screenshots that help clarify the task. -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banCharField/varchar_valid.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_no_diagnostics 9 | CREATE TABLE "core_bar" ( 10 | "id" serial NOT NULL PRIMARY KEY, 11 | "alpha" varchar(100) NOT NULL 12 | ); 13 | 14 | ``` 15 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/valid_single_alter.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test single ALTER TABLE statement (should be safe) 9 | -- expect_no_diagnostics 10 | ALTER TABLE authors ALTER COLUMN name SET NOT NULL; 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexDeletion/concurrent_valid.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test concurrent index deletion (should be safe) 9 | -- expect_no_diagnostics 10 | DROP INDEX CONCURRENTLY IF EXISTS users_email_idx; 11 | ``` 12 | -------------------------------------------------------------------------------- /crates/pgls_configuration/src/rules/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod configuration; 2 | pub(crate) mod selector; 3 | 4 | pub use configuration::{ 5 | RuleAssistConfiguration, RuleAssistPlainConfiguration, RuleAssistWithOptions, 6 | RuleConfiguration, RuleFixConfiguration, RulePlainConfiguration, RuleWithFixOptions, 7 | RuleWithOptions, 8 | }; 9 | pub use selector::RuleSelector; 10 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__quoted_ident_with_escape_quote.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n\"foo \"\" bar\"\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "\"foo \"\" bar\"" @ QuotedIdent { terminated: true }, 9 | "\n" @ LineEnding { count: 1 }, 10 | ] 11 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__quoted_ident_with_escape_quote.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n\"foo \"\" bar\"\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "\"foo \"\" bar\"" @ QuotedIdent { terminated: true }, 9 | "\n" @ LineEnding { count: 1 }, 10 | ] 11 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexCreation/concurrent_valid.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test concurrent index creation (should be safe) 9 | -- expect_no_diagnostics 10 | CREATE INDEX CONCURRENTLY users_email_idx ON users (email); 11 | ``` 12 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/valid_new_table.sql: -------------------------------------------------------------------------------- 1 | -- Valid: ALTER TABLE on newly created table doesn't hold ACCESS EXCLUSIVE 2 | -- expect_no_diagnostics 3 | CREATE TABLE products ( 4 | id INT PRIMARY KEY, 5 | name TEXT 6 | ); 7 | 8 | ALTER TABLE products ADD COLUMN price DECIMAL(10, 2); 9 | SELECT COUNT(*) FROM products; 10 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_unfinished_1.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected end of input 2 | --> tests/markup/open_element_unfinished_1.rs:2:5 3 | | 4 | 2 | / pgls_console::markup! { 5 | 3 | | < 6 | 4 | | } 7 | | |_____^ 8 | | 9 | = note: this error originates in the macro `pgls_console::markup` (in Nightly builds, run with -Z macro-backtrace for more info) 10 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/valid_generated_virtual.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test adding regular column with integer type (should be safe) 9 | -- expect_no_diagnostics 10 | ALTER TABLE prices ADD COLUMN quantity integer DEFAULT 0; 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/multiple_statements.sql: -------------------------------------------------------------------------------- 1 | -- All statements after ALTER TABLE should trigger 2 | ALTER TABLE users ADD COLUMN age INT; 3 | 4 | -- expect_lint/safety/runningStatementWhileHoldingAccessExclusive 5 | UPDATE users SET age = 30; 6 | 7 | -- expect_lint/safety/runningStatementWhileHoldingAccessExclusive 8 | SELECT * FROM users; 9 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/valid_create_table.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: Creating a regular table (not an enum) 9 | -- expect_no_diagnostics 10 | CREATE TABLE users ( 11 | id INT PRIMARY KEY, 12 | name TEXT NOT NULL 13 | ); 14 | 15 | ``` 16 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/timeout_set_variant.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: SET lock_timeout (without LOCAL) also works 9 | -- expect_no_diagnostics 10 | SET lock_timeout = '1s'; 11 | ALTER TABLE books ADD COLUMN author_id INT; 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/valid_concurrent_index.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: CREATE INDEX CONCURRENTLY doesn't take dangerous locks 9 | -- expect_no_diagnostics 10 | CREATE INDEX CONCURRENTLY books_title_idx ON books(title); 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/valid_with_timeout.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: Lock timeout is set before ALTER TABLE 9 | -- expect_no_diagnostics 10 | SET LOCAL lock_timeout = '2s'; 11 | ALTER TABLE authors ADD COLUMN email TEXT; 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /crates/pgls_console/tests/markup/open_element_unfinished_2.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected end of input 2 | --> tests/markup/open_element_unfinished_2.rs:2:5 3 | | 4 | 2 | / pgls_console::markup! { 5 | 3 | | tests/markup/open_element_unfinished_3.rs:2:5 3 | | 4 | 2 | / pgls_console::markup! { 5 | 3 | | tests/markup/open_element_unfinished_4.rs:2:5 3 | | 4 | 2 | / pgls_console::markup! { 5 | 3 | | tests/markup/open_element_unfinished_5.rs:2:5 3 | | 4 | 2 | / pgls_console::markup! { 5 | 3 | | tests/markup/open_element_unfinished_6.rs:2:5 3 | | 4 | 2 | / pgls_console::markup! { 5 | 3 | | tests/markup/open_element_unfinished_7.rs:2:5 3 | | 4 | 2 | / pgls_console::markup! { 5 | 3 | | io::Result<()>; 12 | fn write_fmt(&mut self, elements: &MarkupElements, content: fmt::Arguments) -> io::Result<()>; 13 | } 14 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexCreation/new_table_valid.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test index on newly created table (should be safe) 9 | -- expect_no_diagnostics 10 | CREATE TABLE users (id serial, email text); 11 | CREATE INDEX users_email_idx ON users (email); 12 | ``` 13 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/constraintMissingNotValid/with_not_valid.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test constraint with NOT VALID (should be safe) 9 | -- expect_no_diagnostics 10 | ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses (address) NOT VALID; 11 | ``` 12 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/valid_combined.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test single ALTER TABLE with multiple actions (should be safe) 9 | -- expect_no_diagnostics 10 | ALTER TABLE authors 11 | ALTER COLUMN name SET NOT NULL, 12 | ALTER COLUMN email SET NOT NULL; 13 | 14 | ``` 15 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/valid_different_tables.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test ALTER TABLE on different tables (should be safe) 9 | -- expect_no_diagnostics 10 | ALTER TABLE authors ADD COLUMN bio text; 11 | ALTER TABLE posts ADD COLUMN published_at timestamp; 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/valid_before_alter.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: Statements before ALTER TABLE are fine 9 | -- expect_no_diagnostics 10 | SELECT COUNT(*) FROM authors; 11 | CREATE INDEX authors_name_idx ON authors(name); 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /crates/pgls_fs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # pgls_fs 2 | 3 | mod dir; 4 | mod fs; 5 | mod interner; 6 | mod path; 7 | 8 | pub use dir::ensure_cache_dir; 9 | pub use interner::PathInterner; 10 | pub use path::PgLSPath; 11 | 12 | pub use fs::{ 13 | AutoSearchResult, ConfigName, ErrorEntry, File, FileSystem, FileSystemDiagnostic, 14 | FileSystemExt, MemoryFileSystem, OpenOptions, OsFileSystem, TraversalContext, TraversalScope, 15 | }; 16 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/builtin_now.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select now() from users 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `pg_catalog.now() → timestamp with time zone` 13 | ```plain 14 | Function - Stable - Security INVOKER 15 | ``` 16 | --- 17 | ```sql 18 | 19 | ``` 20 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/column_hover_quoted_column_name.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select "email" from auth.users 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `auth.users.email` 13 | ```plain 14 | varchar(255) - not null 15 | 16 | ``` 17 | --- 18 | ```plain 19 | 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/table_hover.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select id from users 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.users` - 🔓 RLS disabled 13 | ```plain 14 | 15 | ``` 16 | --- 17 | ```plain 18 | 19 | ~0 rows, ~0 dead rows, 8.19 kB 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_schema_cache/src/queries/versions.sql: -------------------------------------------------------------------------------- 1 | select 2 | version(), 3 | current_setting('server_version_num') :: int8 AS version_num, 4 | current_setting('server_version_num') :: int8 / 10000 AS major_version, 5 | ( 6 | select 7 | count(*) :: int8 AS active_connections 8 | FROM 9 | pg_stat_activity 10 | ) AS active_connections, 11 | current_setting('max_connections') :: int8 AS max_connections; 12 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferRobustStmts/robust_statements.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test proper robust statements (should be safe) 9 | -- expect_no_diagnostics 10 | CREATE INDEX CONCURRENTLY IF NOT EXISTS users_email_idx ON users (email); 11 | DROP INDEX CONCURRENTLY IF EXISTS old_idx; 12 | ``` 13 | -------------------------------------------------------------------------------- /crates/pgls_cli/tests/fixtures/postgres-language-server.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://pg-language-server.com/schema/postgres-language-server.schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignore": [] 10 | }, 11 | "linter": { 12 | "enabled": true, 13 | "rules": { 14 | "recommended": true 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/grant_select.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | grant select on users to public; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.users` - 🔓 RLS disabled 13 | ```plain 14 | 15 | ``` 16 | --- 17 | ```plain 18 | 19 | ~0 rows, ~0 dead rows, 16.38 kB 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/hover_on_schemas.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select * from auth.users; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `auth` - owned by postgres 13 | ```plain 14 | 15 | ``` 16 | --- 17 | ```plain 18 | 19 | ~16 kB, 1 tables, 0 views, 0 functions 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/role_alter.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | alter role test_login set work_mem = '256MB' 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `test_login` 13 | ```plain 14 | Permissions: 15 | - 🔑 can login 16 | 17 | 18 | ``` 19 | --- 20 | ```plain 21 | 22 | ``` 23 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__dollar_strings_part2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nDO $doblock$\nend\n$doblock$;\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "DO" @ Ident, 9 | " " @ Space, 10 | "$doblock$\nend\n$doblock$" @ Literal { kind: DollarQuotedString { terminated: true } }, 11 | ";" @ Semi, 12 | ] 13 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__dollar_strings_part2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nDO $doblock$\nend\n$doblock$;\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "DO" @ Ident, 9 | " " @ Space, 10 | "$doblock$\nend\n$doblock$" @ Literal { kind: DollarQuotedString { terminated: true } }, 11 | ";" @ Semi, 12 | ] 13 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "postgres-language-server" 3 | version = "0.1.0" 4 | description = "A collection of language tools and a Language Server Protocol (LSP) implementation for Postgres, focusing on developer experience and reliable SQL tooling." 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "mike>=2.1.3", 9 | "mkdocs>=1.6.1", 10 | "mkdocs-github-admonitions-plugin>=0.0.3", 11 | ] 12 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/revoke_select.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | revoke select on users from public; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.users` - 🔓 RLS disabled 13 | ```plain 14 | 15 | ``` 16 | --- 17 | ```plain 18 | 19 | ~0 rows, ~0 dead rows, 16.38 kB 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/table_hover_quoted_schema.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select * from "auth".users 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `auth.users` - 🔓 RLS disabled 13 | ```plain 14 | 15 | ``` 16 | --- 17 | ```plain 18 | 19 | ~0 rows, ~0 dead rows, 8.19 kB 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__select_with_period.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nselect public.users;\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "select" @ Ident, 9 | " " @ Space, 10 | "public" @ Ident, 11 | "." @ Dot, 12 | "users" @ Ident, 13 | ";" @ Semi, 14 | "\n" @ LineEnding { count: 1 }, 15 | ] 16 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__select_with_period.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nselect public.users;\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "select" @ Ident, 9 | " " @ Space, 10 | "public" @ Ident, 11 | "." @ Dot, 12 | "users" @ Ident, 13 | ";" @ Semi, 14 | "\n" @ LineEnding { count: 1 }, 15 | ] 16 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/valid_lookup_table.sql: -------------------------------------------------------------------------------- 1 | -- Valid: Using a lookup table instead of enum 2 | -- expect_no_diagnostics 3 | CREATE TABLE document_type ( 4 | type_name TEXT PRIMARY KEY 5 | ); 6 | 7 | INSERT INTO document_type VALUES ('invoice'), ('receipt'), ('other'); 8 | 9 | CREATE TABLE document ( 10 | id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 11 | type TEXT REFERENCES document_type(type_name) 12 | ); 13 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/multipleAlterTable/valid_different_schemas.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test ALTER TABLE on tables with same name but different schemas (should be safe) 9 | -- expect_no_diagnostics 10 | ALTER TABLE public.users ADD COLUMN age integer; 11 | ALTER TABLE admin.users ADD COLUMN age integer; 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /crates/pgls_cli/src/execute/stdin.rs: -------------------------------------------------------------------------------- 1 | use crate::execute::StdinPayload; 2 | use crate::execute::config::ExecutionConfig; 3 | use crate::{CliDiagnostic, CliSession}; 4 | use pgls_console::{ConsoleExt, markup}; 5 | 6 | pub(crate) fn process( 7 | session: &mut CliSession, 8 | _config: &ExecutionConfig, 9 | payload: StdinPayload, 10 | ) -> Result<(), CliDiagnostic> { 11 | session.console().append(markup! {{payload.content}}); 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/table_hover_quoted_table_name.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select * from "auth"."users" 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `auth.users` - 🔓 RLS disabled 13 | ```plain 14 | 15 | ``` 16 | --- 17 | ```plain 18 | 19 | ~0 rows, ~0 dead rows, 8.19 kB 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_completions/src/relevance.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod filtering; 2 | pub(crate) mod scoring; 3 | 4 | #[derive(Debug, Clone)] 5 | pub(crate) enum CompletionRelevanceData<'a> { 6 | Table(&'a pgls_schema_cache::Table), 7 | Function(&'a pgls_schema_cache::Function), 8 | Column(&'a pgls_schema_cache::Column), 9 | Schema(&'a pgls_schema_cache::Schema), 10 | Policy(&'a pgls_schema_cache::Policy), 11 | Role(&'a pgls_schema_cache::Role), 12 | } 13 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/column_hover.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select users.id from users 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.users.id` 13 | ```plain 14 | int4 - 🔑 primary key - not null 15 | 16 | ``` 17 | --- 18 | ```plain 19 | 20 | Default: nextval('users_id_seq'::regclass) 21 | ``` 22 | -------------------------------------------------------------------------------- /crates/pgls_treesitter_grammar/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | 6 | [*.{json,toml,yml,gyp}] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.js] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.{c,cc,h}] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [*.rs] 19 | indent_style = space 20 | indent_size = 4 21 | 22 | [parser.c] 23 | indent_size = 2 24 | 25 | [{alloc,array,parser}.h] 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/valid_create_table.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test CREATE TABLE with serial column (should be safe, rule only applies to ALTER TABLE) 9 | -- expect_no_diagnostics 10 | CREATE TABLE products ( 11 | id serial PRIMARY KEY, 12 | name text NOT NULL, 13 | price numeric 14 | ); 15 | 16 | ``` 17 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/hover_type_in_select_clause.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select (compfoo).f1 from some_table s; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.compfoo` (Custom Type) 13 | ```plain 14 | Attributes: 15 | - f1: int4 16 | - f2: text 17 | 18 | 19 | ``` 20 | --- 21 | ```plain 22 | 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /crates/pgls_markup/README.md: -------------------------------------------------------------------------------- 1 | # `pgls_markup` 2 | 3 | The crate contains procedural macros to build `pgls_console` markup object with a JSX-like syntax 4 | 5 | The macro cannot be used alone as it generates code that requires supporting types declared in the 6 | `pgls_console` crate, so it's re-exported from there and should be used as `pgls_console::markup` 7 | 8 | ## Acknowledgement 9 | 10 | This crate was initially forked from [biome](https://github.com/biomejs/biome). 11 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__quoted_ident.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n\"hello &1 -world\";\n\n\n\"hello-world\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "\"hello &1 -world\"" @ QuotedIdent { terminated: true }, 9 | ";" @ Semi, 10 | "\n\n\n" @ LineEnding { count: 3 }, 11 | "\"hello-world\n" @ QuotedIdent { terminated: false }, 12 | ] 13 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__quoted_ident.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n\"hello &1 -world\";\n\n\n\"hello-world\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "\"hello &1 -world\"" @ QuotedIdent { terminated: true }, 9 | ";" @ Semi, 10 | "\n\n\n" @ LineEnding { count: 3 }, 11 | "\"hello-world\n" @ QuotedIdent { terminated: false }, 12 | ] 13 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/valid_new_table.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: ALTER TABLE on a table created in the same transaction doesn't need timeout 9 | -- expect_no_diagnostics 10 | CREATE TABLE users ( 11 | id INT PRIMARY KEY, 12 | name TEXT 13 | ); 14 | 15 | ALTER TABLE users ADD COLUMN email TEXT; 16 | 17 | ``` 18 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_tokenizer" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | 16 | [dev-dependencies] 17 | insta.workspace = true 18 | 19 | [lib] 20 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/create_policy.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | create policy "my cool pol" on users for all to public with check (true); 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.users` - 🔓 RLS disabled 13 | ```plain 14 | 15 | ``` 16 | --- 17 | ```plain 18 | 19 | ~0 rows, ~0 dead rows, 16.38 kB 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/valid_index_on_new_table.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: CREATE INDEX on a table created in the same transaction doesn't need timeout 9 | -- expect_no_diagnostics 10 | CREATE TABLE products ( 11 | id INT PRIMARY KEY, 12 | name TEXT 13 | ); 14 | 15 | CREATE INDEX products_name_idx ON products(name); 16 | 17 | ``` 18 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/column_hover_quoted_schema_table.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select "auth"."user_profiles".first_name from "auth"."user_profiles" 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `auth.user_profiles.first_name` 13 | ```plain 14 | varchar(100) - nullable 15 | 16 | ``` 17 | --- 18 | ```plain 19 | 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_statement_splitter/src/splitter/ddl.rs: -------------------------------------------------------------------------------- 1 | use pgls_lexer::SyntaxKind; 2 | 3 | use crate::splitter::common::SplitterResult; 4 | 5 | use super::{Splitter, common::unknown}; 6 | 7 | pub(crate) fn create(p: &mut Splitter) -> SplitterResult { 8 | p.expect(SyntaxKind::CREATE_KW)?; 9 | 10 | unknown(p, &[SyntaxKind::WITH_KW]) 11 | } 12 | 13 | pub(crate) fn alter(p: &mut Splitter) -> SplitterResult { 14 | p.expect(SyntaxKind::ALTER_KW)?; 15 | 16 | unknown(p, &[SyntaxKind::ALTER_KW]) 17 | } 18 | -------------------------------------------------------------------------------- /crates/pgls_env/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "Environment variables and configuration for Postgres Tools" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_env" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | pgls_console = { workspace = true } 16 | 17 | [dev-dependencies] 18 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__dollar_quote_mismatch_tags_simple.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n-- dollar quoting with mismatched tags\n$foo$hello world$bar$\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "-- dollar quoting with mismatched tags" @ LineComment, 9 | "\n" @ LineEnding { count: 1 }, 10 | "$foo$hello world$bar$\n" @ Literal { kind: DollarQuotedString { terminated: false } }, 11 | ] 12 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__dollar_quote_mismatch_tags_simple.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n-- dollar quoting with mismatched tags\n$foo$hello world$bar$\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "-- dollar quoting with mismatched tags" @ LineComment, 9 | "\n" @ LineEnding { count: 1 }, 10 | "$foo$hello world$bar$\n" @ Literal { kind: DollarQuotedString { terminated: false } }, 11 | ] 12 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/column_hover_join.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select * from users u join posts p on u.id = p.user_id 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.posts.user_id` 13 | ```plain 14 | int4 - not null 15 | 16 | ``` 17 | --- 18 | ```plain 19 | 20 | Default: nextval('posts_user_id_seq'::regclass) 21 | ``` 22 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__dollar_quote_mismatch_tags_complex.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n-- with dollar inside but mismatched tags\n$foo$hello$world$bar$\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "-- with dollar inside but mismatched tags" @ LineComment, 9 | "\n" @ LineEnding { count: 1 }, 10 | "$foo$hello$world$bar$\n" @ Literal { kind: DollarQuotedString { terminated: false } }, 11 | ] 12 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__dollar_quote_mismatch_tags_complex.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n-- with dollar inside but mismatched tags\n$foo$hello$world$bar$\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "-- with dollar inside but mismatched tags" @ LineComment, 9 | "\n" @ LineEnding { count: 1 }, 10 | "$foo$hello$world$bar$\n" @ Literal { kind: DollarQuotedString { terminated: false } }, 11 | ] 12 | -------------------------------------------------------------------------------- /docs/codegen/src/version.rs: -------------------------------------------------------------------------------- 1 | use pgls_env::VERSION; 2 | use std::{fs, path::Path}; 3 | 4 | use regex::Regex; 5 | 6 | pub fn replace_version(docs_dir: &Path) -> anyhow::Result<()> { 7 | let index_path = docs_dir.join("getting_started.md"); 8 | 9 | let data = fs::read_to_string(&index_path)?; 10 | 11 | let version_pattern = Regex::new(r"\$\{PGLS_VERSION\}").unwrap(); 12 | let new_data = version_pattern.replace_all(&data, VERSION); 13 | 14 | fs::write(&index_path, new_data.as_ref())?; 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banDropDatabase/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banDropDatabase 9 | drop database all_users; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/banDropDatabase ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Dropping a database may break existing clients. 16 | 17 | i You probably don't want to drop your database. 18 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/column_hover_quoted_column_name_with_table.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select phone, id from users join phone_nums on "users"."email" = phone_nums.email; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.users.email` 13 | ```plain 14 | varchar(255) - not null 15 | 16 | ``` 17 | --- 18 | ```plain 19 | 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/pgls_query/src/node_structs.rs: -------------------------------------------------------------------------------- 1 | use protobuf::Node; 2 | 3 | pgls_query_macros::node_structs_codegen!(); 4 | 5 | impl Node { 6 | pub fn deparse(&self) -> Result { 7 | crate::deparse(&protobuf::ParseResult { 8 | version: crate::bindings::PG_VERSION_NUM as i32, 9 | stmts: vec![protobuf::RawStmt { 10 | stmt: Some(Box::new(self.clone())), 11 | stmt_location: 0, 12 | stmt_len: 0, 13 | }], 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/valid_new_table.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: ALTER TABLE on newly created table doesn't hold ACCESS EXCLUSIVE 9 | -- expect_no_diagnostics 10 | CREATE TABLE products ( 11 | id INT PRIMARY KEY, 12 | name TEXT 13 | ); 14 | 15 | ALTER TABLE products ADD COLUMN price DECIMAL(10, 2); 16 | SELECT COUNT(*) FROM products; 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature request 4 | url: https://github.com/supabase/postgres_lsp/discussions/categories/ideas 5 | about: Request a new feature or example. 6 | - name: Ask a question 7 | url: https://github.com/supabase/postgres_lsp/discussions/categories/q-a 8 | about: Ask questions and discuss with other community members. 9 | - name: Want to work with us? 10 | url: https://supabase.io/humans.txt 11 | about: Want to work with us? Get in touch! 12 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/hover_custom_type_enum.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | create function getfoo() returns setof compfoo as $$ select fooid, fooname from foo $$ language sql; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.compfoo` (Custom Type) 13 | ```plain 14 | Enum Permutations: 15 | - yes 16 | - no 17 | 18 | 19 | ``` 20 | --- 21 | ```plain 22 | 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /crates/pgls_query_ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_query_ext" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | pgls_diagnostics.workspace = true 16 | pgls_query.workspace = true 17 | pgls_text_size.workspace = true 18 | 19 | [lib] 20 | doctest = false 21 | -------------------------------------------------------------------------------- /crates/pgls_type_resolver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_type_resolver" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | pgls_query.workspace = true 16 | pgls_schema_cache.workspace = true 17 | 18 | [dev-dependencies] 19 | 20 | [lib] 21 | doctest = false 22 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/hover_custom_type_with_properties.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | create function getfoo() returns setof compfoo as $$ select fooid, fooname from foo $$ language sql; 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.compfoo` (Custom Type) 13 | ```plain 14 | Attributes: 15 | - f1: int4 16 | - f2: text 17 | 18 | 19 | ``` 20 | --- 21 | ```plain 22 | 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/codegen/src/cli_doc.rs: -------------------------------------------------------------------------------- 1 | use pgls_cli::pg_l_s_command; 2 | use std::{fs, path::Path}; 3 | 4 | use crate::utils; 5 | 6 | pub fn generate_cli_doc(docs_dir: &Path) -> anyhow::Result<()> { 7 | let file_path = docs_dir.join("reference/cli.md"); 8 | 9 | let content = fs::read_to_string(&file_path)?; 10 | 11 | let new_content = utils::replace_section( 12 | &content, 13 | "CLI_REF", 14 | &pg_l_s_command().render_markdown("postgres-language-server"), 15 | ); 16 | 17 | fs::write(file_path, &new_content)?; 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferRobustStmts/drop_without_if_exists.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferRobustStmts 9 | DROP INDEX CONCURRENTLY users_email_idx; 10 | 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/preferRobustStmts ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Concurrent drop should use IF EXISTS. 17 | 18 | i Add IF EXISTS to make the migration re-runnable if it fails. 19 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/lenghty_function.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select public.func() 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.func(cool_stuff text, something_else integer, a_third_thing text) → void` 13 | ```plain 14 | Function - Stable - Security INVOKER 15 | ``` 16 | --- 17 | ```sql 18 | public.func( 19 | cool_stuff text, 20 | something_else integer, 21 | a_third_thing text 22 | ) 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /crates/pgls_workspace_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_workspace_macros" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = { version = "1.0.95" } 18 | quote = { workspace = true } 19 | syn = { workspace = true } 20 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/renamingColumn/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/renamingColumn 9 | ALTER TABLE users RENAME COLUMN name TO full_name; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/renamingColumn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Renaming a column may break existing clients. 16 | 17 | i Consider creating a new column with the desired name and migrating data instead. 18 | -------------------------------------------------------------------------------- /crates/pgls_cli/src/commands/clean.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::daemon::default_pgls_log_path; 2 | use crate::{CliDiagnostic, CliSession}; 3 | use pgls_env::pgls_env; 4 | use std::fs::{create_dir, remove_dir_all}; 5 | use std::path::PathBuf; 6 | 7 | /// Runs the clean command 8 | pub fn clean(_cli_session: CliSession) -> Result<(), CliDiagnostic> { 9 | let logs_path = pgls_env() 10 | .pgls_log_path 11 | .value() 12 | .map_or(default_pgls_log_path(), PathBuf::from); 13 | remove_dir_all(logs_path.clone()).and_then(|_| create_dir(logs_path))?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /crates/pgls_splinter/tests/snapshots/no_primary_key.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_splinter/tests/diagnostics.rs 3 | expression: content 4 | snapshot_kind: text 5 | --- 6 | Category: splinter/performance/noPrimaryKey 7 | Severity: Information 8 | Message: Table \`public.articles\` does not have a primary key 9 | Advices: 10 | Detects if a table does not have a primary key. Tables without a primary key can be inefficient to interact with at scale. 11 | [Info] table: public.articles 12 | [Info] Remediation: https://supabase.com/docs/guides/database/database-linter?lint=0004_no_primary_key 13 | -------------------------------------------------------------------------------- /crates/pgls_text_size/tests/constructors.rs: -------------------------------------------------------------------------------- 1 | use pgls_text_size::TextSize; 2 | 3 | #[derive(Copy, Clone)] 4 | struct BadRope<'a>(&'a [&'a str]); 5 | 6 | impl BadRope<'_> { 7 | fn text_len(self) -> TextSize { 8 | self.0.iter().copied().map(TextSize::of).sum() 9 | } 10 | } 11 | 12 | #[test] 13 | fn main() { 14 | let x: char = 'c'; 15 | let _ = TextSize::of(x); 16 | 17 | let x: &str = "hello"; 18 | let _ = TextSize::of(x); 19 | 20 | let x: &String = &"hello".into(); 21 | let _ = TextSize::of(x); 22 | 23 | let _ = BadRope(&[""]).text_len(); 24 | } 25 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banDropTable/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banDropTable 9 | drop table test; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/banDropTable ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Dropping a table may break existing clients. 16 | 17 | i Update your application code to no longer read or write the table, and only then delete the table. Be sure to create a backup. 18 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__line_comment_whitespace.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nselect 'Hello' -- This is a comment\n' World';\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "select" @ Ident, 9 | " " @ Space, 10 | "'Hello'" @ Literal { kind: Str { terminated: true } }, 11 | " " @ Space, 12 | "-- This is a comment" @ LineComment, 13 | "\n" @ LineEnding { count: 1 }, 14 | "' World'" @ Literal { kind: Str { terminated: true } }, 15 | ";" @ Semi, 16 | ] 17 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__line_comment_whitespace.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nselect 'Hello' -- This is a comment\n' World';\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "select" @ Ident, 9 | " " @ Space, 10 | "'Hello'" @ Literal { kind: Str { terminated: true } }, 11 | " " @ Space, 12 | "-- This is a comment" @ LineComment, 13 | "\n" @ LineEnding { count: 1 }, 14 | "' World'" @ Literal { kind: Str { terminated: true } }, 15 | ";" @ Semi, 16 | ] 17 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banDropColumn/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banDropColumn 9 | alter table test 10 | drop column id; 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/banDropColumn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Dropping a column may break existing clients. 17 | 18 | i You can leave the column as nullable or delete the column once queries no longer select or modify the column. 19 | -------------------------------------------------------------------------------- /crates/pgls_cli/tests/snapshots/assert_check__check_github_reporter_snapshot.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_cli/tests/assert_check.rs 3 | expression: "run_check(&[\"--reporter\", \"github\", \"tests/fixtures/test.sql\"])" 4 | snapshot_kind: text 5 | --- 6 | status: failure 7 | stdout: 8 | ::error title=syntax,file=tests/fixtures/test.sql,line=1,endLine=1,col=1,endColumn=35::Invalid statement: syntax error at or near "tqjable" 9 | stderr: 10 | check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11 | 12 | × Some errors were emitted while running checks. 13 | -------------------------------------------------------------------------------- /crates/pgls_text_size/tests/auto_traits.rs: -------------------------------------------------------------------------------- 1 | use { 2 | pgls_text_size::*, 3 | static_assertions::*, 4 | std::{ 5 | fmt::Debug, 6 | hash::Hash, 7 | marker::{Send, Sync}, 8 | panic::{RefUnwindSafe, UnwindSafe}, 9 | }, 10 | }; 11 | 12 | // auto traits 13 | assert_impl_all!(TextSize: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe); 14 | assert_impl_all!(TextRange: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe); 15 | 16 | // common traits 17 | assert_impl_all!(TextSize: Copy, Debug, Default, Hash, Ord); 18 | assert_impl_all!(TextRange: Copy, Debug, Default, Hash, Eq); 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2.Improve_docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Improve documentation 3 | about: Suggest an improvement to our documentation 4 | labels: documentation 5 | --- 6 | 7 | # Improve documentation 8 | 9 | ## Link 10 | 11 | Add a link to the page which needs improvement (if relevant) 12 | 13 | ## Describe the problem 14 | 15 | Is the documentation missing? Or is it confusing? Why is it confusing? 16 | 17 | ## Describe the improvement 18 | 19 | A clear and concise description of the improvement. 20 | 21 | ## Additional context 22 | 23 | Add any other context or screenshots that help clarify your question. -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferRobustStmts/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferRobustStmts 9 | CREATE INDEX CONCURRENTLY users_email_idx ON users (email); 10 | SELECT 1; 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/preferRobustStmts ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Concurrent index creation should use IF NOT EXISTS. 17 | 18 | i Add IF NOT EXISTS to make the migration re-runnable if it fails. 19 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/builtin_count.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select count(*) from users 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `pg_catalog.count("any") → bigint` 13 | ```plain 14 | Aggregate - Immutable - Security INVOKER 15 | ``` 16 | --- 17 | ```sql 18 | 19 | ``` 20 | 21 | --- 22 | 23 | ### `pg_catalog.count() → bigint` 24 | ```plain 25 | Aggregate - Immutable - Security INVOKER 26 | ``` 27 | --- 28 | ```sql 29 | 30 | ``` 31 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/valid_lookup_table.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Valid: Using a lookup table instead of enum 9 | -- expect_no_diagnostics 10 | CREATE TABLE document_type ( 11 | type_name TEXT PRIMARY KEY 12 | ); 13 | 14 | INSERT INTO document_type VALUES ('invoice'), ('receipt'), ('other'); 15 | 16 | CREATE TABLE document ( 17 | id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 18 | type TEXT REFERENCES document_type(type_name) 19 | ); 20 | 21 | ``` 22 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | license = "MIT OR Apache-2.0" 4 | name = "xtask" 5 | publish = false 6 | rust-version.workspace = true 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | anyhow = "1.0.62" 11 | flate2 = "1.0.24" 12 | time = { version = "0.3", default-features = false } 13 | write-json = "0.1.2" 14 | xflags = "0.3.0" 15 | xshell = "0.2.2" 16 | zip = { version = "0.6", default-features = false, features = ["deflate", "time"] } 17 | # Avoid adding more dependencies to this crate 18 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/renamingTable/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/renamingTable 9 | ALTER TABLE users RENAME TO customers; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/renamingTable ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Renaming a table may break existing clients. 16 | 17 | i Consider creating a view with the old table name instead, or coordinate the rename carefully with application deployments. 18 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__named_param_at.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | "@id" @ NamedParam { kind: AtPrefix }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__named_param_at.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | "@id" @ NamedParam { kind: AtPrefix }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /.github/actions/free-disk-space/action.yml: -------------------------------------------------------------------------------- 1 | name: Free Disk Space 2 | description: Free up disk space on the runner 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Free Disk Space (Ubuntu) 7 | if: runner.os == 'Linux' 8 | uses: jlumbroso/free-disk-space@main 9 | with: 10 | # We need to reclaim some space, but uninstalling everyting takes 11 | # too long. So we'll just remove some of the larger packages. 12 | # https://github.com/jlumbroso/free-disk-space/pull/26 13 | android: true 14 | dotnet: true 15 | haskell: true 16 | large-packages: false 17 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banTruncateCascade/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banTruncateCascade 9 | truncate a cascade; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/banTruncateCascade ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × The `CASCADE` option will also truncate any tables that are foreign-keyed to the specified tables. 16 | 17 | i Do not use the `CASCADE` option. Instead, specify manually what you want: `TRUNCATE a, b;`. 18 | -------------------------------------------------------------------------------- /crates/pgls_markup/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_markup" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | proc-macro-error = { version = "1.0.4", default-features = false } 16 | proc-macro2 = { workspace = true } 17 | quote = "1.0.14" 18 | 19 | [dev-dependencies] 20 | 21 | [lib] 22 | proc-macro = true 23 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__named_param_colon_raw.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | ":id" @ NamedParam { kind: ColonRaw }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__named_param_colon_raw.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | ":id" @ NamedParam { kind: ColonRaw }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__named_param_dollar_raw.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | "$id" @ NamedParam { kind: DollarRaw }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__named_param_dollar_raw.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | "$id" @ NamedParam { kind: DollarRaw }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:15 2 | 3 | # Install build dependencies 4 | RUN apt-get update && \ 5 | apt-get install -y postgresql-server-dev-15 gcc make git && \ 6 | cd /tmp && \ 7 | git clone https://github.com/okbob/plpgsql_check.git && \ 8 | cd plpgsql_check && \ 9 | make && \ 10 | make install && \ 11 | apt-get remove -y postgresql-server-dev-15 gcc make git && \ 12 | apt-get autoremove -y && \ 13 | rm -rf /tmp/plpgsql_check /var/lib/apt/lists/* 14 | 15 | # Add initialization script directly 16 | RUN echo "CREATE EXTENSION IF NOT EXISTS plpgsql_check;" > /docker-entrypoint-initdb.d/01-create-extension.sql -------------------------------------------------------------------------------- /biome.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": true 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [], 11 | "include": ["/packages/**/*"] 12 | }, 13 | "formatter": { 14 | "enabled": true, 15 | "indentStyle": "tab" 16 | }, 17 | "organizeImports": { 18 | "enabled": true 19 | }, 20 | "linter": { 21 | "enabled": true, 22 | "rules": { 23 | "recommended": true 24 | } 25 | }, 26 | "javascript": { 27 | "formatter": { 28 | "quoteStyle": "double" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexCreation/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/requireConcurrentIndexCreation 9 | CREATE INDEX users_email_idx ON users (email); 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/requireConcurrentIndexCreation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Creating an index non-concurrently blocks writes to the table. 16 | 17 | i Use CREATE INDEX CONCURRENTLY to avoid blocking concurrent operations on the table. 18 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/requireConcurrentIndexDeletion/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/requireConcurrentIndexDeletion 9 | DROP INDEX IF EXISTS users_email_idx; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/requireConcurrentIndexDeletion ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Dropping an index non-concurrently blocks reads and writes to the table. 16 | 17 | i Use DROP INDEX CONCURRENTLY to avoid blocking concurrent operations on the table. 18 | -------------------------------------------------------------------------------- /crates/pgls_lsp/src/documents.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::line_index::LineIndex; 2 | 3 | /// Represents an open [`textDocument`]. Can be cheaply cloned. 4 | /// 5 | /// [`textDocument`]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem 6 | #[derive(Clone)] 7 | pub(crate) struct Document { 8 | pub(crate) version: i32, 9 | pub(crate) line_index: LineIndex, 10 | } 11 | 12 | impl Document { 13 | pub(crate) fn new(version: i32, text: &str) -> Self { 14 | Self { 15 | version, 16 | line_index: LineIndex::new(text), 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /crates/pgls_treesitter_grammar/.gitignore: -------------------------------------------------------------------------------- 1 | # Rust artifacts 2 | target/ 3 | 4 | # Node artifacts 5 | build/ 6 | prebuilds/ 7 | node_modules/ 8 | 9 | # Swift artifacts 10 | .build/ 11 | 12 | # Go artifacts 13 | _obj/ 14 | 15 | # Python artifacts 16 | .venv/ 17 | dist/ 18 | *.egg-info 19 | *.whl 20 | 21 | # C artifacts 22 | *.a 23 | *.so 24 | *.so.* 25 | *.dylib 26 | *.dll 27 | *.pc 28 | *.exp 29 | *.lib 30 | 31 | # Zig artifacts 32 | .zig-cache/ 33 | zig-cache/ 34 | zig-out/ 35 | 36 | # Example dirs 37 | /examples/*/ 38 | 39 | # Grammar volatiles 40 | *.wasm 41 | *.obj 42 | *.o 43 | 44 | # Archives 45 | *.tar.gz 46 | *.tgz 47 | *.zip 48 | -------------------------------------------------------------------------------- /crates/pgls_lexer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_lexer" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | pgls_diagnostics.workspace = true 16 | pgls_lexer_codegen.workspace = true 17 | pgls_text_size.workspace = true 18 | pgls_tokenizer.workspace = true 19 | 20 | [dev-dependencies] 21 | insta.workspace = true 22 | 23 | [lib] 24 | -------------------------------------------------------------------------------- /editors/code/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/strictest/tsconfig.json", 3 | "compilerOptions": { 4 | "esModuleInterop": false, 5 | "module": "Node16", 6 | "moduleResolution": "node16", 7 | "target": "es2021", 8 | "outDir": "out", 9 | "lib": ["es2021"], 10 | "sourceMap": true, 11 | "rootDir": ".", 12 | "newLine": "LF", 13 | 14 | // FIXME: https://github.com/rust-lang/rust-analyzer/issues/15253 15 | "exactOptionalPropertyTypes": false 16 | }, 17 | "exclude": ["node_modules", ".vscode-test"], 18 | "include": ["src", "tests"] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@postgrestools/monorepo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "devDependencies": { 6 | "@biomejs/biome": "1.9.4", 7 | "@types/bun": "latest" 8 | }, 9 | "peerDependencies": { 10 | "typescript": "^5" 11 | }, 12 | "workspaces": [ 13 | "packages/@postgrestools/postgrestools", 14 | "packages/@postgrestools/backend-jsonrpc", 15 | "packages/@postgres-language-server/cli", 16 | "packages/@postgres-language-server/backend-jsonrpc" 17 | ], 18 | "keywords": [], 19 | "author": "Supabase Community", 20 | "license": "MIT OR Apache-2.0", 21 | "packageManager": "bun@1" 22 | } 23 | -------------------------------------------------------------------------------- /postgrestools.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./docs/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignore": [] 10 | }, 11 | "linter": { 12 | "enabled": true, 13 | "rules": { 14 | "recommended": true 15 | } 16 | }, 17 | // YOU CAN COMMENT ME OUT :) 18 | "db": { 19 | "host": "127.0.0.1", 20 | "port": 5432, 21 | "username": "postgres", 22 | "password": "postgres", 23 | "database": "postgres", 24 | "connTimeoutSecs": 10, 25 | "allowStatementExecutionsAgainst": ["127.0.0.1/*", "localhost/*"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__named_param_colon_string.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | ":'id'" @ NamedParam { kind: ColonString { terminated: true } }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__named_param_colon_string.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | ":'id'" @ NamedParam { kind: ColonString { terminated: true } }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/transactionNesting/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/transactionNesting 9 | BEGIN; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/transactionNesting ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Transaction already managed by migration tool. 16 | 17 | i Migration tools manage transactions automatically. Remove explicit transaction control. 18 | 19 | i Put migration statements in separate files to have them be in separate transactions. 20 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__named_param_colon_identifier.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | ":\"id\"" @ NamedParam { kind: ColonIdentifier { terminated: true } }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__named_param_colon_identifier.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | " " @ Space, 19 | "=" @ Eq, 20 | " " @ Space, 21 | ":\"id\"" @ NamedParam { kind: ColonIdentifier { terminated: true } }, 22 | ";" @ Semi, 23 | ] 24 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/function_with_schema.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select test_schema.schema_func('test') 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `test_schema.schema_func(text) → text` 13 | ```plain 14 | Function - Stable - Security INVOKER 15 | ``` 16 | --- 17 | ```sql 18 | 19 | CREATE OR REPLACE FUNCTION test_schema.schema_func(text) 20 | RETURNS text 21 | LANGUAGE sql 22 | STABLE 23 | AS $function$ 24 | select $1 || ' processed'; 25 | $function$ 26 | 27 | 28 | ``` 29 | -------------------------------------------------------------------------------- /crates/pgls_schema_cache/src/extensions.rs: -------------------------------------------------------------------------------- 1 | use sqlx::PgPool; 2 | 3 | use crate::schema_cache::SchemaCacheItem; 4 | 5 | #[derive(Debug, Default)] 6 | pub struct Extension { 7 | pub name: String, 8 | pub schema: Option, 9 | pub default_version: String, 10 | pub installed_version: Option, 11 | pub comment: Option, 12 | } 13 | 14 | impl SchemaCacheItem for Extension { 15 | type Item = Extension; 16 | 17 | async fn load(pool: &PgPool) -> Result, sqlx::Error> { 18 | sqlx::query_file_as!(Extension, "src/queries/extensions.sql") 19 | .fetch_all(pool) 20 | .await 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/reference/rules/ban-drop-database.md: -------------------------------------------------------------------------------- 1 | # banDropDatabase 2 | **Diagnostic Category: `lint/safety/banDropDatabase`** 3 | 4 | **Since**: `vnext` 5 | 6 | 7 | **Sources**: 8 | - Inspired from: squawk/ban-drop-database 9 | 10 | ## Description 11 | Dropping a database may break existing clients (and everything else, really). 12 | 13 | Make sure that you really want to drop it. 14 | 15 | ## How to configure 16 | ```json 17 | 18 | { 19 | "linter": { 20 | "rules": { 21 | "safety": { 22 | "banDropDatabase": "error" 23 | } 24 | } 25 | } 26 | } 27 | 28 | ``` 29 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/changingColumnType/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/changingColumnType 9 | ALTER TABLE "core_recipe" ALTER COLUMN "edits" TYPE text USING "edits"::text; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/changingColumnType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Changing a column type requires a table rewrite and blocks reads and writes. 16 | 17 | i Consider creating a new column with the desired type, migrating data, and then dropping the old column. 18 | -------------------------------------------------------------------------------- /crates/pgls_splinter/tests/snapshots/policy_exists_rls_disabled.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_splinter/tests/diagnostics.rs 3 | expression: content 4 | snapshot_kind: text 5 | --- 6 | Category: splinter/security/policyExistsRlsDisabled 7 | Severity: Error 8 | Message: Table \`public.documents\` has RLS policies but RLS is not enabled on the table. Policies include {documents_policy}. 9 | Advices: 10 | Detects cases where row level security (RLS) policies have been created, but RLS has not been enabled for the underlying table. 11 | [Info] table: public.documents 12 | [Info] Remediation: https://supabase.com/docs/guides/database/database-linter?lint=0007_policy_exists_rls_disabled 13 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferBigInt/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferBigInt 9 | CREATE TABLE users ( 10 | id integer 11 | ); 12 | 13 | ``` 14 | 15 | # Diagnostics 16 | lint/safety/preferBigInt ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17 | 18 | × Using smaller integer types can lead to overflow issues. 19 | 20 | i The 'int4' type has a limited range that may be exceeded as your data grows. 21 | 22 | i Consider using BIGINT for integer columns to avoid future migration issues. 23 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferBigintOverInt/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferBigintOverInt 9 | CREATE TABLE users ( 10 | id integer 11 | ); 12 | 13 | ``` 14 | 15 | # Diagnostics 16 | lint/safety/preferBigintOverInt ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17 | 18 | × INTEGER type may lead to overflow issues. 19 | 20 | i INTEGER has a maximum value of 2,147,483,647 which can be exceeded by ID columns and counters. 21 | 22 | i Consider using BIGINT instead for better future-proofing. 23 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferIdentity/alter_table.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferIdentity 9 | alter table test add column id serial; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/preferIdentity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Prefer IDENTITY columns over SERIAL types. 16 | 17 | i Column uses 'serial' type which has limitations compared to IDENTITY columns. 18 | 19 | i Use 'bigint GENERATED BY DEFAULT AS IDENTITY' or 'bigint GENERATED ALWAYS AS IDENTITY' instead. 20 | -------------------------------------------------------------------------------- /crates/pgls_test_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | authors.workspace = true 4 | categories.workspace = true 5 | description = "" 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "pgls_test_macros" 11 | repository.workspace = true 12 | version = "0.0.0" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | globwalk = { version = "0.9.1" } 19 | proc-macro-error = { version = "1.0.4" } 20 | proc-macro2 = { version = '1.0.93' } 21 | quote = { workspace = true } 22 | syn = { workspace = true } 23 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__named_param_colon_raw_vs_cast.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | "::" @ DoubleColon, 19 | "test" @ Ident, 20 | " " @ Space, 21 | "=" @ Eq, 22 | " " @ Space, 23 | ":id" @ NamedParam { kind: ColonRaw }, 24 | ";" @ Semi, 25 | ] 26 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingPrimaryKeyConstraint/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addingPrimaryKeyConstraint 9 | ALTER TABLE users ADD PRIMARY KEY (id); 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/addingPrimaryKeyConstraint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Adding a PRIMARY KEY constraint results in locks and table rewrites. 16 | 17 | i Adding a PRIMARY KEY constraint requires an ACCESS EXCLUSIVE lock which blocks reads. 18 | 19 | i Add the PRIMARY KEY constraint USING an index. 20 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banCharField/bpchar.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banCharField 9 | CREATE TABLE test ( 10 | code bpchar(5) 11 | ); 12 | 13 | ``` 14 | 15 | # Diagnostics 16 | lint/safety/banCharField ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17 | 18 | × CHAR type is discouraged due to space padding behavior. 19 | 20 | i CHAR types are fixed-length and padded with spaces, which can lead to unexpected behavior. 21 | 22 | i Use VARCHAR or TEXT instead for variable-length character data. 23 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferIdentity/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferIdentity 9 | create table users ( 10 | id serial 11 | ); 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/preferIdentity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Prefer IDENTITY columns over SERIAL types. 18 | 19 | i Column uses 'serial' type which has limitations compared to IDENTITY columns. 20 | 21 | i Use 'bigint GENERATED BY DEFAULT AS IDENTITY' or 'bigint GENERATED ALWAYS AS IDENTITY' instead. 22 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__named_param_colon_raw_vs_cast.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "select" @ Ident, 8 | " " @ Space, 9 | "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, 10 | " " @ Space, 11 | "from" @ Ident, 12 | " " @ Space, 13 | "c" @ Ident, 14 | " " @ Space, 15 | "where" @ Ident, 16 | " " @ Space, 17 | "id" @ Ident, 18 | "::" @ DoubleColon, 19 | "test" @ Ident, 20 | " " @ Space, 21 | "=" @ Eq, 22 | " " @ Space, 23 | ":id" @ NamedParam { kind: ColonRaw }, 24 | ";" @ Semi, 25 | ] 26 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banCharField/alter_table.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banCharField 9 | ALTER TABLE users ADD COLUMN code character(10); 10 | 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/banCharField ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × CHAR type is discouraged due to space padding behavior. 17 | 18 | i CHAR types are fixed-length and padded with spaces, which can lead to unexpected behavior. 19 | 20 | i Use VARCHAR or TEXT instead for variable-length character data. 21 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/transactionNesting/commit.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | SELECT 1; 9 | -- expect_lint/safety/transactionNesting 10 | COMMIT; 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/transactionNesting ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Attempting to end transaction managed by migration tool. 18 | 19 | i Migration tools manage transactions automatically. Remove explicit transaction control. 20 | 21 | i Put migration statements in separate files to have them be in separate transactions. 22 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/transactionNesting/rollback.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | SELECT 1; 9 | -- expect_lint/safety/transactionNesting 10 | ROLLBACK; 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/transactionNesting ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Attempting to end transaction managed by migration tool. 18 | 19 | i Migration tools manage transactions automatically. Remove explicit transaction control. 20 | 21 | i Put migration statements in separate files to have them be in separate transactions. 22 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__bitstring.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nB'1001'\nb'1001'\nX'1FF'\nx'1FF'\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "B'1001'" @ Literal { kind: BitStr { terminated: true } }, 9 | "\n" @ LineEnding { count: 1 }, 10 | "b'1001'" @ Literal { kind: BitStr { terminated: true } }, 11 | "\n" @ LineEnding { count: 1 }, 12 | "X'1FF'" @ Literal { kind: ByteStr { terminated: true } }, 13 | "\n" @ LineEnding { count: 1 }, 14 | "x'1FF'" @ Literal { kind: ByteStr { terminated: true } }, 15 | "\n" @ LineEnding { count: 1 }, 16 | ] 17 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__bitstring.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nB'1001'\nb'1001'\nX'1FF'\nx'1FF'\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "B'1001'" @ Literal { kind: BitStr { terminated: true } }, 9 | "\n" @ LineEnding { count: 1 }, 10 | "b'1001'" @ Literal { kind: BitStr { terminated: true } }, 11 | "\n" @ LineEnding { count: 1 }, 12 | "X'1FF'" @ Literal { kind: ByteStr { terminated: true } }, 13 | "\n" @ LineEnding { count: 1 }, 14 | "x'1FF'" @ Literal { kind: ByteStr { terminated: true } }, 15 | "\n" @ LineEnding { count: 1 }, 16 | ] 17 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferIdentity/bigserial.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferIdentity 9 | create table users ( 10 | id bigserial 11 | ); 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/preferIdentity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Prefer IDENTITY columns over SERIAL types. 18 | 19 | i Column uses 'bigserial' type which has limitations compared to IDENTITY columns. 20 | 21 | i Use 'bigint GENERATED BY DEFAULT AS IDENTITY' or 'bigint GENERATED ALWAYS AS IDENTITY' instead. 22 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/custom_function.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select custom_add(1, 2) 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `public.custom_add(a integer, b integer) → integer` 13 | ```plain 14 | Function - Immutable - Security INVOKER 15 | ``` 16 | --- 17 | ```sql 18 | 19 | CREATE OR REPLACE FUNCTION public.custom_add(a integer, b integer) 20 | RETURNS integer 21 | LANGUAGE plpgsql 22 | IMMUTABLE 23 | AS $function$ 24 | begin 25 | return a + b; 26 | end; 27 | $function$ 28 | 29 | 30 | ``` 31 | -------------------------------------------------------------------------------- /crates/pgls_suppressions/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | authors.workspace = true 4 | categories.workspace = true 5 | description = "Provides an API that parses suppressions from SQL files, and provides a way to check if a diagnostic is suppressed." 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "pgls_suppressions" 11 | repository.workspace = true 12 | version = "0.0.0" 13 | 14 | [dependencies] 15 | pgls_analyse = { workspace = true } 16 | pgls_diagnostics = { workspace = true } 17 | pgls_text_size = { workspace = true } 18 | tracing = { workspace = true } 19 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__graphile_named_param.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "grant" @ Ident, 8 | " " @ Space, 9 | "usage" @ Ident, 10 | " " @ Space, 11 | "on" @ Ident, 12 | " " @ Space, 13 | "schema" @ Ident, 14 | " " @ Space, 15 | "public" @ Ident, 16 | "," @ Comma, 17 | " " @ Space, 18 | "app_public" @ Ident, 19 | "," @ Comma, 20 | " " @ Space, 21 | "app_hidden" @ Ident, 22 | " " @ Space, 23 | "to" @ Ident, 24 | " " @ Space, 25 | ":DATABASE_VISITOR" @ NamedParam { kind: ColonRaw }, 26 | ";" @ Semi, 27 | ] 28 | -------------------------------------------------------------------------------- /test.sql: -------------------------------------------------------------------------------- 1 | create table 2 | unknown_users (id serial primary key, address text, email text); 3 | 4 | drop table unknown_users; 5 | 6 | select 7 | * 8 | from 9 | unknown_users; 10 | 11 | sel 1; 12 | 13 | 14 | 15 | create function test_organisation_id () 16 | returns setof text 17 | language plpgsql 18 | security invoker 19 | as $$ 20 | declre 21 | v_organisation_id uuid; 22 | begin 23 | return next is(private.organisation_id(), v_organisation_id, 'should return organisation_id of token'); 24 | end 25 | $$; 26 | 27 | 28 | create function f1() 29 | returns void as $$ 30 | declare b constant int; 31 | begin 32 | call p1(10, b); 33 | end; 34 | $$ language plpgsql; 35 | -------------------------------------------------------------------------------- /crates/pgls_diagnostics_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_diagnostics_macros" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | proc-macro-error = { version = "1.0.4", default-features = false } 19 | proc-macro2 = { workspace = true } 20 | quote = { workspace = true } 21 | syn = { workspace = true } 22 | 23 | [dev-dependencies] 24 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__graphile_named_param.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: result 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "grant" @ Ident, 8 | " " @ Space, 9 | "usage" @ Ident, 10 | " " @ Space, 11 | "on" @ Ident, 12 | " " @ Space, 13 | "schema" @ Ident, 14 | " " @ Space, 15 | "public" @ Ident, 16 | "," @ Comma, 17 | " " @ Space, 18 | "app_public" @ Ident, 19 | "," @ Comma, 20 | " " @ Space, 21 | "app_hidden" @ Ident, 22 | " " @ Space, 23 | "to" @ Ident, 24 | " " @ Space, 25 | ":DATABASE_VISITOR" @ NamedParam { kind: ColonRaw }, 26 | ";" @ Semi, 27 | ] 28 | -------------------------------------------------------------------------------- /docs/codegen/src/default_configuration.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::Path}; 2 | 3 | use crate::utils::replace_section; 4 | 5 | use pgls_configuration::PartialConfiguration; 6 | 7 | pub fn generate_default_configuration(docs_dir: &Path) -> anyhow::Result<()> { 8 | let index_path = docs_dir.join("getting_started.md"); 9 | 10 | let printed_config = format!( 11 | "\n```json\n{}\n```\n", 12 | serde_json::to_string_pretty(&PartialConfiguration::init())? 13 | ); 14 | 15 | let data = fs::read_to_string(&index_path)?; 16 | 17 | let new_data = replace_section(&data, "DEFAULT_CONFIGURATION", &printed_config); 18 | 19 | fs::write(&index_path, new_data)?; 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingPrimaryKeyConstraint/serial_column.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addingPrimaryKeyConstraint 9 | ALTER TABLE items ADD COLUMN id SERIAL PRIMARY KEY; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/addingPrimaryKeyConstraint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Adding a PRIMARY KEY constraint results in locks and table rewrites. 16 | 17 | i Adding a PRIMARY KEY constraint requires an ACCESS EXCLUSIVE lock which blocks reads. 18 | 19 | i Add the PRIMARY KEY constraint USING an index. 20 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banDropNotNull/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banDropNotNull 9 | alter table users 10 | alter column id 11 | drop not null; 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/banDropNotNull ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Dropping a NOT NULL constraint may break existing clients. 18 | 19 | i Consider using a marker value that represents NULL. Alternatively, create a new table allowing NULL values, copy the data from the old table, and create a view that filters NULL values. 20 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/constraintMissingNotValid/check_constraint.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/constraintMissingNotValid 9 | ALTER TABLE users ADD CONSTRAINT check_age CHECK (age >= 0); 10 | 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/constraintMissingNotValid ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Adding a constraint without NOT VALID will block reads and writes while validating existing rows. 17 | 18 | i Add the constraint as NOT VALID in one transaction, then run VALIDATE CONSTRAINT in a separate transaction. 19 | -------------------------------------------------------------------------------- /crates/pgls_diagnostics_categories/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_diagnostics_categories" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | pgls_env = { workspace = true } 16 | schemars = { workspace = true, optional = true } 17 | serde = { workspace = true, optional = true } 18 | 19 | [features] 20 | schema = ["dep:schemars"] 21 | serde = ["dep:serde"] 22 | 23 | [build-dependencies] 24 | quote = "1.0.14" 25 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferBigintOverSmallint/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferBigintOverSmallint 9 | CREATE TABLE users ( 10 | age smallint 11 | ); 12 | 13 | ``` 14 | 15 | # Diagnostics 16 | lint/safety/preferBigintOverSmallint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17 | 18 | × SMALLINT has a very limited range that is easily exceeded. 19 | 20 | i SMALLINT can only store values from -32,768 to 32,767. This range is often insufficient. 21 | 22 | i Consider using INTEGER or BIGINT for better range and future-proofing. 23 | -------------------------------------------------------------------------------- /crates/pgls_query_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_query_macros" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | [dependencies] 14 | convert_case = { workspace = true } 15 | proc-macro2.workspace = true 16 | prost-reflect = { workspace = true } 17 | protox = { workspace = true } 18 | quote.workspace = true 19 | 20 | [lib] 21 | proc-macro = true 22 | 23 | [build-dependencies] 24 | ureq = "2.9" 25 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addingFieldWithDefault 9 | ALTER TABLE "core_recipe" ADD COLUMN "foo" integer DEFAULT 10; 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/addingFieldWithDefault ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Adding a column with a DEFAULT value causes a table rewrite. 16 | 17 | i This operation requires an ACCESS EXCLUSIVE lock and rewrites the entire table. 18 | 19 | i Add the column without a default, then set the default in a separate statement. 20 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/constraintMissingNotValid/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/constraintMissingNotValid 9 | ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses (address); 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/constraintMissingNotValid ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Adding a constraint without NOT VALID will block reads and writes while validating existing rows. 16 | 17 | i Add the constraint as NOT VALID in one transaction, then run VALIDATE CONSTRAINT in a separate transaction. 18 | -------------------------------------------------------------------------------- /crates/pgls_treesitter_grammar/tests/snapshots/grammar_tests__test_4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_treesitter_grammar/tests/grammar_tests.rs 3 | expression: writer 4 | --- 5 | select "auth".REPLACED_TOKEN 6 | ----------------------- 7 | program [0..28] 'select "auth".REPLACED_TOKEN' 8 | statement [0..28] 'select "auth".REPLACED_TOKEN' 9 | select [0..28] 'select "auth".REPLACED_TOKEN' 10 | keyword_select [0..6] 'select' 11 | select_expression [7..28] '"auth".REPLACED_TOKEN' 12 | term [7..28] '"auth".REPLACED_TOKEN' 13 | object_reference [7..28] '"auth".REPLACED_TOKEN' 14 | any_identifier [7..13] '"auth"' 15 | . [13..14] '.' 16 | any_identifier [14..28] 'REPLACED_TOKEN' 17 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/volatile_default.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addingFieldWithDefault 9 | ALTER TABLE users ADD COLUMN created_at timestamp DEFAULT now(); 10 | 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/addingFieldWithDefault ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Adding a column with a DEFAULT value causes a table rewrite. 17 | 18 | i This operation requires an ACCESS EXCLUSIVE lock and rewrites the entire table. 19 | 20 | i Add the column without a default, then set the default in a separate statement. 21 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingNotNullField/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addingNotNullField 9 | ALTER TABLE "core_recipe" ALTER COLUMN "foo" SET NOT NULL; 10 | 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/addingNotNullField ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Setting a column NOT NULL blocks reads while the table is scanned. 17 | 18 | i This operation requires an ACCESS EXCLUSIVE lock and a full table scan to verify all rows. 19 | 20 | i Use a CHECK constraint with NOT VALID instead, then validate it in a separate transaction. 21 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banCharField/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banCharField 9 | CREATE TABLE "core_bar" ( 10 | "id" serial NOT NULL PRIMARY KEY, 11 | "alpha" char(100) NOT NULL 12 | ); 13 | ``` 14 | 15 | # Diagnostics 16 | lint/safety/banCharField ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17 | 18 | × CHAR type is discouraged due to space padding behavior. 19 | 20 | i CHAR types are fixed-length and padded with spaces, which can lead to unexpected behavior. 21 | 22 | i Use VARCHAR or TEXT instead for variable-length character data. 23 | -------------------------------------------------------------------------------- /crates/pgls_splinter/tests/snapshots/unindexed_foreign_key.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_splinter/tests/diagnostics.rs 3 | expression: content 4 | snapshot_kind: text 5 | --- 6 | Category: splinter/performance/unindexedForeignKeys 7 | Severity: Information 8 | Message: Table \`public.posts\` has a foreign key \`posts_user_id_fkey\` without a covering index. This can lead to suboptimal query performance. 9 | Advices: 10 | Identifies foreign key constraints without a covering index, which can impact database performance. 11 | [Info] table: public.posts 12 | {"fkey_name":"posts_user_id_fkey","fkey_columns":[2]} 13 | [Info] Remediation: https://supabase.com/docs/guides/database/database-linter?lint=0001_unindexed_foreign_keys 14 | -------------------------------------------------------------------------------- /crates/pgls_schema_cache/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The schema cache 2 | 3 | #![allow(dead_code)] 4 | 5 | mod columns; 6 | mod extensions; 7 | mod functions; 8 | mod policies; 9 | mod roles; 10 | mod schema_cache; 11 | mod schemas; 12 | mod tables; 13 | mod triggers; 14 | mod types; 15 | mod versions; 16 | 17 | pub use columns::*; 18 | pub use extensions::Extension; 19 | pub use functions::{Behavior, Function, FunctionArg, FunctionArgs, ProcKind}; 20 | pub use policies::{Policy, PolicyCommand}; 21 | pub use roles::*; 22 | pub use schema_cache::SchemaCache; 23 | pub use schemas::Schema; 24 | pub use tables::{ReplicaIdentity, Table, TableKind}; 25 | pub use triggers::{Trigger, TriggerAffected, TriggerEvent}; 26 | pub use types::{PostgresType, PostgresTypeAttribute}; 27 | -------------------------------------------------------------------------------- /xtask/rules_check/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "Internal script to make sure that the metadata or the rules are correct" 3 | edition = "2021" 4 | name = "rules_check" 5 | publish = false 6 | version = "0.0.0" 7 | 8 | [dependencies] 9 | anyhow = { workspace = true } 10 | pgls_analyse = { workspace = true } 11 | pgls_analyser = { workspace = true } 12 | pgls_console = { workspace = true } 13 | pgls_diagnostics = { workspace = true } 14 | pgls_query = { workspace = true } 15 | pgls_query_ext = { workspace = true } 16 | pgls_statement_splitter = { workspace = true } 17 | pgls_workspace = { workspace = true } 18 | pulldown-cmark = "0.12.2" 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/pgls_lexer_codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_lexer_codegen" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | [dependencies] 14 | anyhow = { workspace = true } 15 | convert_case = { workspace = true } 16 | proc-macro2.workspace = true 17 | prost-reflect = { workspace = true } 18 | protox = { workspace = true } 19 | quote.workspace = true 20 | 21 | [build-dependencies] 22 | ureq = "2.9" 23 | 24 | [lib] 25 | proc-macro = true 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | 17 | # File system 18 | .DS_Store 19 | desktop.ini 20 | 21 | *.log 22 | 23 | node_modules/ 24 | 25 | **/dist/ 26 | .claude-session-id 27 | 28 | squawk/ 29 | eugene/ 30 | 31 | # Auto generated treesitter files 32 | crates/pgt_treesitter_grammar/src/grammar.json 33 | crates/pgt_treesitter_grammar/src/node-types.json 34 | crates/pgt_treesitter_grammar/src/parser.c 35 | crates/pgt_treesitter_grammar/src/parser.c.codex-session-id 36 | .codex-session-id 37 | -------------------------------------------------------------------------------- /crates/pgls_schema_cache/src/queries/triggers.sql: -------------------------------------------------------------------------------- 1 | -- we need to join tables from the pg_catalog since "TRUNCATE" triggers are 2 | -- not available in the information_schema.trigger table. 3 | select 4 | t.tgname as "name!", 5 | c.relname as "table_name!", 6 | p.proname as "proc_name!", 7 | proc_ns.nspname as "proc_schema!", 8 | table_ns.nspname as "table_schema!", 9 | t.tgtype as "details_bitmask!" 10 | from 11 | pg_catalog.pg_trigger t 12 | left join pg_catalog.pg_proc p on t.tgfoid = p.oid 13 | left join pg_catalog.pg_class c on t.tgrelid = c.oid 14 | left join pg_catalog.pg_namespace table_ns on c.relnamespace = table_ns.oid 15 | left join pg_catalog.pg_namespace proc_ns on p.pronamespace = proc_ns.oid 16 | where 17 | t.tgisinternal = false and 18 | t.tgconstraint = 0; 19 | -------------------------------------------------------------------------------- /crates/pgls_treesitter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_treesitter" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | [lib] 14 | doctest = false 15 | 16 | [dependencies] 17 | clap = { version = "4.5.23", features = ["derive"] } 18 | pgls_schema_cache.workspace = true 19 | pgls_text_size.workspace = true 20 | pgls_treesitter_grammar.workspace = true 21 | tree-sitter.workspace = true 22 | 23 | [dev-dependencies] 24 | pgls_test_utils.workspace = true 25 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/banConcurrentIndexCreationInTransaction/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/banConcurrentIndexCreationInTransaction 9 | CREATE INDEX CONCURRENTLY "field_name_idx" ON "table_name" ("field_name"); 10 | SELECT 1; 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/banConcurrentIndexCreationInTransaction ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × CREATE INDEX CONCURRENTLY cannot be used inside a transaction block. 17 | 18 | i Run CREATE INDEX CONCURRENTLY outside of a transaction. Migration tools usually run in transactions, so you may need to run this statement in its own migration or manually. 19 | -------------------------------------------------------------------------------- /crates/pgls_schema_cache/src/schemas.rs: -------------------------------------------------------------------------------- 1 | use sqlx::PgPool; 2 | 3 | use crate::schema_cache::SchemaCacheItem; 4 | 5 | #[derive(Debug, Default)] 6 | pub struct Schema { 7 | pub id: i64, 8 | pub name: String, 9 | pub owner: String, 10 | pub allowed_users: Vec, 11 | pub allowed_creators: Vec, 12 | pub table_count: i64, 13 | pub view_count: i64, 14 | pub function_count: i64, 15 | pub total_size: String, 16 | pub comment: Option, 17 | } 18 | 19 | impl SchemaCacheItem for Schema { 20 | type Item = Schema; 21 | 22 | async fn load(pool: &PgPool) -> Result, sqlx::Error> { 23 | sqlx::query_file_as!(Schema, "src/queries/schemas.sql") 24 | .fetch_all(pool) 25 | .await 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/pgls_splinter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_splinter" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | [dependencies] 14 | pgls_diagnostics.workspace = true 15 | serde.workspace = true 16 | serde_json.workspace = true 17 | sqlx.workspace = true 18 | 19 | [build-dependencies] 20 | ureq = "2.10" 21 | 22 | [dev-dependencies] 23 | insta.workspace = true 24 | pgls_console.workspace = true 25 | pgls_test_utils.workspace = true 26 | 27 | [lib] 28 | doctest = false 29 | -------------------------------------------------------------------------------- /crates/pgls_statement_splitter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_statement_splitter" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | pgls_diagnostics = { workspace = true } 16 | pgls_lexer.workspace = true 17 | pgls_query.workspace = true 18 | pgls_text_size.workspace = true 19 | regex.workspace = true 20 | 21 | [dev-dependencies] 22 | criterion.workspace = true 23 | ntest = "0.9.3" 24 | 25 | [[bench]] 26 | harness = false 27 | name = "splitter" 28 | -------------------------------------------------------------------------------- /crates/pgls_hover/tests/snapshots/function_hover_quoted_schema.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_hover/tests/hover_integration_tests.rs 3 | expression: snapshot 4 | --- 5 | # Input 6 | ```sql 7 | select "auth".authenticate_user('test@example.com') 8 | ↑ hovered here 9 | ``` 10 | 11 | # Hover Results 12 | ### `auth.authenticate_user(user_email text) → boolean` 13 | ```plain 14 | Function - Security DEFINER 15 | ``` 16 | --- 17 | ```sql 18 | 19 | CREATE OR REPLACE FUNCTION auth.authenticate_user(user_email text) 20 | RETURNS boolean 21 | LANGUAGE plpgsql 22 | SECURITY DEFINER 23 | AS $function$ 24 | begin 25 | return exists(select 1 from auth.users where email = user_email); 26 | end; 27 | $function$ 28 | 29 | 30 | ``` 31 | -------------------------------------------------------------------------------- /crates/pgls_test_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_test_utils" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [[bin]] 15 | name = "tree_print" 16 | path = "src/bin/tree_print.rs" 17 | 18 | [dependencies] 19 | anyhow = "1.0.81" 20 | clap = { version = "4.5.23", features = ["derive"] } 21 | dotenv = "0.15.0" 22 | uuid = { version = "1.11.0", features = ["v4"] } 23 | 24 | pgls_treesitter_grammar.workspace = true 25 | sqlx.workspace = true 26 | tree-sitter.workspace = true 27 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/generated_column.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addingFieldWithDefault 9 | ALTER TABLE users ADD COLUMN full_name text GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED; 10 | 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/addingFieldWithDefault ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Adding a generated column requires a table rewrite. 17 | 18 | i This operation requires an ACCESS EXCLUSIVE lock and rewrites the entire table. 19 | 20 | i Add the column as nullable, backfill existing rows, and add a trigger to update the column on write instead. 21 | -------------------------------------------------------------------------------- /crates/pgls_cli/tests/snapshots/assert_check__check_default_reporter_snapshot.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_cli/tests/assert_check.rs 3 | expression: "run_check(&[\"tests/fixtures/test.sql\"])" 4 | snapshot_kind: text 5 | --- 6 | status: failure 7 | stdout: 8 | Checked 1 file in . No fixes applied. 9 | Found 1 error. 10 | stderr: 11 | tests/fixtures/test.sql:1:1 syntax ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12 | 13 | × Invalid statement: syntax error at or near "tqjable" 14 | 15 | > 1 │ alter tqjable test drop column id; 16 | │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 17 | 2 │ 18 | 19 | 20 | check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 21 | 22 | × Some errors were emitted while running checks. 23 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_only_lint/safety/creatingEnum 9 | CREATE TYPE document_type AS ENUM ('invoice', 'receipt', 'other'); 10 | ``` 11 | 12 | # Diagnostics 13 | lint/safety/creatingEnum ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | 15 | × Creating enum type document_type is not recommended. 16 | 17 | i Enum types are difficult to modify: removing values requires complex migrations, and associating additional data with values is not possible. 18 | 19 | i Consider using a lookup table with a foreign key constraint instead, which provides more flexibility and easier maintenance. 20 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/preferJsonb/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/preferJsonb 9 | CREATE TABLE users ( 10 | id integer, 11 | data json 12 | ); 13 | 14 | ``` 15 | 16 | # Diagnostics 17 | lint/safety/preferJsonb ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 18 | 19 | × Prefer JSONB over JSON for better performance and functionality. 20 | 21 | i JSON stores exact text representation while JSONB stores parsed binary format. JSONB is faster for queries, supports indexing, and removes duplicate keys. 22 | 23 | i Consider using JSONB instead unless you specifically need to preserve formatting or duplicate keys. 24 | -------------------------------------------------------------------------------- /crates/pgls_configuration/src/migrations.rs: -------------------------------------------------------------------------------- 1 | use biome_deserialize_macros::{Merge, Partial}; 2 | use bpaf::Bpaf; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The configuration of the filesystem 6 | #[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize, Default)] 7 | #[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))] 8 | #[partial(serde(rename_all = "camelCase", default, deny_unknown_fields))] 9 | #[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))] 10 | pub struct MigrationsConfiguration { 11 | /// The directory where the migration files are stored 12 | #[partial(bpaf(long("migrations-dir")))] 13 | pub migrations_dir: String, 14 | 15 | /// Ignore any migrations before this timestamp 16 | #[partial(bpaf(long("after")))] 17 | pub after: u64, 18 | } 19 | -------------------------------------------------------------------------------- /crates/pgls_query/src/node_mut.rs: -------------------------------------------------------------------------------- 1 | use protobuf::Node; 2 | 3 | pgls_query_macros::node_mut_codegen!(); 4 | 5 | impl NodeMut { 6 | pub fn deparse(&self) -> Result { 7 | crate::deparse(&protobuf::ParseResult { 8 | version: crate::bindings::PG_VERSION_NUM as i32, 9 | stmts: vec![protobuf::RawStmt { 10 | stmt: Some(Box::new(Node { 11 | node: Some(self.to_enum()?), 12 | })), 13 | stmt_location: 0, 14 | stmt_len: 0, 15 | }], 16 | }) 17 | } 18 | 19 | pub fn nodes_mut(&self) -> Vec { 20 | self.iter_mut().collect() 21 | } 22 | 23 | pub fn iter_mut(&self) -> NodeMutIterator { 24 | NodeMutIterator::new(*self) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/pgls_text_size/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_text_size" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | [dependencies] 14 | schemars = { workspace = true, optional = true } 15 | serde = { workspace = true, optional = true } 16 | 17 | [features] 18 | schema = ["dep:schemars"] 19 | serde = ["dep:serde"] 20 | 21 | [dev-dependencies] 22 | serde_test = "1.0" 23 | static_assertions = "1.1" 24 | 25 | [[test]] 26 | name = "serde" 27 | path = "tests/serde.rs" 28 | required-features = ["serde"] 29 | -------------------------------------------------------------------------------- /crates/pgls_cli/tests/snapshots/assert_check__check_gitlab_reporter_snapshot.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_cli/tests/assert_check.rs 3 | expression: "run_check(&[\"--reporter\", \"gitlab\", \"tests/fixtures/test.sql\"])" 4 | snapshot_kind: text 5 | --- 6 | status: failure 7 | stdout: 8 | [ 9 | { 10 | "description": "Invalid statement: syntax error at or near \"tqjable\"", 11 | "check_name": "syntax", 12 | "fingerprint": "15806099827984337215", 13 | "severity": "critical", 14 | "location": { 15 | "path": "tests/fixtures/test.sql", 16 | "lines": { 17 | "begin": 1 18 | } 19 | } 20 | } 21 | ] 22 | stderr: 23 | check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 24 | 25 | × Some errors were emitted while running checks. 26 | -------------------------------------------------------------------------------- /crates/pgls_query/src/node_ref.rs: -------------------------------------------------------------------------------- 1 | use protobuf::Node; 2 | 3 | pgls_query_macros::node_ref_codegen!(); 4 | 5 | impl<'a> NodeRef<'a> { 6 | pub fn deparse(&self) -> Result { 7 | crate::deparse(&protobuf::ParseResult { 8 | version: crate::bindings::PG_VERSION_NUM as i32, 9 | stmts: vec![protobuf::RawStmt { 10 | stmt: Some(Box::new(Node { 11 | node: Some(self.to_enum()), 12 | })), 13 | stmt_location: 0, 14 | stmt_len: 0, 15 | }], 16 | }) 17 | } 18 | 19 | pub fn nodes(&self) -> Vec> { 20 | self.iter().collect() 21 | } 22 | 23 | pub fn iter(&self) -> NodeRefIterator<'a> { 24 | NodeRefIterator::new(*self) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__params.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nselect $1 + $2;\n\nselect $1123123123123;\n\nselect $;\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "select" @ Ident, 9 | " " @ Space, 10 | "$1" @ PositionalParam, 11 | " " @ Space, 12 | "+" @ Plus, 13 | " " @ Space, 14 | "$2" @ PositionalParam, 15 | ";" @ Semi, 16 | "\n\n" @ LineEnding { count: 2 }, 17 | "select" @ Ident, 18 | " " @ Space, 19 | "$1123123123123" @ PositionalParam, 20 | ";" @ Semi, 21 | "\n\n" @ LineEnding { count: 2 }, 22 | "select" @ Ident, 23 | " " @ Space, 24 | "$" @ PositionalParam, 25 | ";" @ Semi, 26 | "\n" @ LineEnding { count: 1 }, 27 | ] 28 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingForeignKeyConstraint/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addingForeignKeyConstraint 9 | ALTER TABLE "email" ADD CONSTRAINT "fk_user" FOREIGN KEY ("user_id") REFERENCES "user" ("id"); 10 | 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/addingForeignKeyConstraint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Adding a foreign key constraint requires a table scan and locks on both tables. 17 | 18 | i This will block writes to both the referencing and referenced tables while PostgreSQL verifies the constraint. 19 | 20 | i Add the constraint as NOT VALID first, then VALIDATE it in a separate transaction. 21 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__params.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\nselect $1 + $2;\n\nselect $1123123123123;\n\nselect $;\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "select" @ Ident, 9 | " " @ Space, 10 | "$1" @ PositionalParam, 11 | " " @ Space, 12 | "+" @ Plus, 13 | " " @ Space, 14 | "$2" @ PositionalParam, 15 | ";" @ Semi, 16 | "\n\n" @ LineEnding { count: 2 }, 17 | "select" @ Ident, 18 | " " @ Space, 19 | "$1123123123123" @ PositionalParam, 20 | ";" @ Semi, 21 | "\n\n" @ LineEnding { count: 2 }, 22 | "select" @ Ident, 23 | " " @ Space, 24 | "$" @ PositionalParam, 25 | ";" @ Semi, 26 | "\n" @ LineEnding { count: 1 }, 27 | ] 28 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addSerialColumn 9 | -- Test adding serial column to existing table 10 | ALTER TABLE prices ADD COLUMN id serial; 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/addSerialColumn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Adding a column with type serial requires a table rewrite. 18 | 19 | i SERIAL types require rewriting the entire table with an ACCESS EXCLUSIVE lock, blocking all reads and writes. 20 | 21 | i SERIAL types cannot be added to existing tables without a full table rewrite. Consider using a non-serial type with a sequence instead. 22 | -------------------------------------------------------------------------------- /crates/pgls_workspace/src/workspace/server/async_helper.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, sync::LazyLock}; 2 | 3 | use tokio::runtime::Runtime; 4 | 5 | use crate::WorkspaceError; 6 | 7 | // Global Tokio Runtime 8 | static RUNTIME: LazyLock = 9 | LazyLock::new(|| Runtime::new().expect("Failed to create Tokio runtime")); 10 | 11 | /// Use this function to run async functions in the workspace, which is a sync trait called from an 12 | /// async context. 13 | /// 14 | /// Checkout https://greptime.com/blogs/2023-03-09-bridging-async-and-sync-rust for details. 15 | pub fn run_async(future: F) -> Result 16 | where 17 | F: Future + Send + 'static, 18 | R: Send + 'static, 19 | { 20 | futures::executor::block_on(async { RUNTIME.spawn(future).await.map_err(|e| e.into()) }) 21 | } 22 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/lockTimeoutWarning/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_only_lint/safety/lockTimeoutWarning 9 | -- ALTER TABLE without lock timeout should trigger the rule 10 | ALTER TABLE authors ADD COLUMN email TEXT; 11 | ``` 12 | 13 | # Diagnostics 14 | lint/safety/lockTimeoutWarning ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15 | 16 | × Statement takes ACCESS EXCLUSIVE lock on public.authors without lock timeout set. 17 | 18 | i This can block all operations on the table indefinitely if another transaction holds a conflicting lock. 19 | 20 | i Run 'SET LOCAL lock_timeout = '2s';' before this statement and retry the migration if it times out. 21 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgls_tokenizer__tests__dollar_quoting.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n$$Dianne's horse$$\n$SomeTag$Dianne's horse$SomeTag$\n\n-- with dollar inside and matching tags\n$foo$hello$world$bar$\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "$$Dianne's horse$$" @ Literal { kind: DollarQuotedString { terminated: true } }, 9 | "\n" @ LineEnding { count: 1 }, 10 | "$SomeTag$Dianne's horse$SomeTag$" @ Literal { kind: DollarQuotedString { terminated: true } }, 11 | "\n\n" @ LineEnding { count: 2 }, 12 | "-- with dollar inside and matching tags" @ LineComment, 13 | "\n" @ LineEnding { count: 1 }, 14 | "$foo$hello$world$bar$\n" @ Literal { kind: DollarQuotedString { terminated: false } }, 15 | ] 16 | -------------------------------------------------------------------------------- /crates/pgls_tokenizer/src/snapshots/pgt_tokenizer__tests__dollar_quoting.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_tokenizer/src/lib.rs 3 | expression: "lex(r#\"\n$$Dianne's horse$$\n$SomeTag$Dianne's horse$SomeTag$\n\n-- with dollar inside and matching tags\n$foo$hello$world$bar$\n\"#)" 4 | snapshot_kind: text 5 | --- 6 | [ 7 | "\n" @ LineEnding { count: 1 }, 8 | "$$Dianne's horse$$" @ Literal { kind: DollarQuotedString { terminated: true } }, 9 | "\n" @ LineEnding { count: 1 }, 10 | "$SomeTag$Dianne's horse$SomeTag$" @ Literal { kind: DollarQuotedString { terminated: true } }, 11 | "\n\n" @ LineEnding { count: 2 }, 12 | "-- with dollar inside and matching tags" @ LineComment, 13 | "\n" @ LineEnding { count: 1 }, 14 | "$foo$hello$world$bar$\n" @ Literal { kind: DollarQuotedString { terminated: false } }, 15 | ] 16 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/simple_enum.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_only_lint/safety/creatingEnum 9 | -- Simple enum with two values 10 | CREATE TYPE status AS ENUM ('active', 'inactive'); 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/creatingEnum ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Creating enum type status is not recommended. 18 | 19 | i Enum types are difficult to modify: removing values requires complex migrations, and associating additional data with values is not possible. 20 | 21 | i Consider using a lookup table with a foreign key constraint instead, which provides more flexibility and easier maintenance. 22 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/basic.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_only_lint/safety/runningStatementWhileHoldingAccessExclusive 9 | -- Running SELECT after ALTER TABLE should trigger the rule 10 | ALTER TABLE authors ADD COLUMN email TEXT NOT NULL; 11 | SELECT COUNT(*) FROM authors; 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/runningStatementWhileHoldingAccessExclusive ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Running statement while holding ACCESS EXCLUSIVE lock. 18 | 19 | i This blocks all access to the table for the duration of this statement. 20 | 21 | i Run this statement in a separate transaction to minimize lock duration. 22 | -------------------------------------------------------------------------------- /crates/pgls_cli/src/commands/init.rs: -------------------------------------------------------------------------------- 1 | use crate::{CliDiagnostic, CliSession}; 2 | use pgls_configuration::PartialConfiguration; 3 | use pgls_console::{ConsoleExt, markup}; 4 | use pgls_fs::ConfigName; 5 | use pgls_workspace::configuration::create_config; 6 | 7 | pub(crate) fn init(mut session: CliSession) -> Result<(), CliDiagnostic> { 8 | let fs = &mut session.app.fs; 9 | let config = &mut PartialConfiguration::init(); 10 | create_config(fs, config)?; 11 | let file_created = ConfigName::pgls_jsonc(); 12 | session.app.console.log(markup! { 13 | " 14 | Welcome to the Postgres Language Server! Let's get you started... 15 | 16 | ""Files created "" 17 | 18 | ""- "{file_created}" 19 | Your project configuration. 20 | " 21 | }); 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /crates/pgls_configuration/src/plpgsql_check.rs: -------------------------------------------------------------------------------- 1 | use biome_deserialize_macros::{Merge, Partial}; 2 | use bpaf::Bpaf; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The configuration for type checking. 6 | #[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize)] 7 | #[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))] 8 | #[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))] 9 | #[partial(serde(rename_all = "camelCase", default, deny_unknown_fields))] 10 | pub struct PlPgSqlCheckConfiguration { 11 | /// if `false`, it disables the feature and pglpgsql_check won't be executed. `true` by default 12 | #[partial(bpaf(hide))] 13 | pub enabled: bool, 14 | } 15 | 16 | impl Default for PlPgSqlCheckConfiguration { 17 | fn default() -> Self { 18 | Self { enabled: true } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/bigserial.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addSerialColumn 9 | -- Test adding bigserial column to existing table 10 | ALTER TABLE prices ADD COLUMN big_id bigserial; 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/addSerialColumn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Adding a column with type bigserial requires a table rewrite. 18 | 19 | i SERIAL types require rewriting the entire table with an ACCESS EXCLUSIVE lock, blocking all reads and writes. 20 | 21 | i SERIAL types cannot be added to existing tables without a full table rewrite. Consider using a non-serial type with a sequence instead. 22 | -------------------------------------------------------------------------------- /crates/pgls_query/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | /// Error structure representing the basic error scenarios for `pg_query`. 4 | #[derive(Debug, Error, Eq, PartialEq)] 5 | pub enum Error { 6 | #[error("Invalid statement format: {0}")] 7 | Conversion(#[from] std::ffi::NulError), 8 | #[error("Error decoding result: {0}")] 9 | Decode(#[from] prost::DecodeError), 10 | #[error("Invalid statement: {0}")] 11 | Parse(String), 12 | #[error("Error parsing JSON: {0}")] 13 | InvalidJson(String), 14 | #[error("Invalid pointer")] 15 | InvalidPointer, 16 | #[error("Error scanning: {0}")] 17 | Scan(String), 18 | #[error("Error splitting: {0}")] 19 | Split(String), 20 | } 21 | 22 | /// Convenient Result alias for returning `pg_query::Error`. 23 | pub type Result = core::result::Result; 24 | -------------------------------------------------------------------------------- /crates/pgls_treesitter_grammar/tests/snapshots/grammar_tests__test_1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgls_treesitter_grammar/tests/grammar_tests.rs 3 | expression: writer 4 | --- 5 | select * from auth.users; 6 | ----------------------- 7 | program [0..25] 'select * from auth.users;' 8 | statement [0..24] 'select * from auth.users' 9 | select [0..8] 'select *' 10 | keyword_select [0..6] 'select' 11 | select_expression [7..8] '*' 12 | term [7..8] '*' 13 | all_fields [7..8] '*' 14 | * [7..8] '*' 15 | from [9..24] 'from auth.users' 16 | keyword_from [9..13] 'from' 17 | relation [14..24] 'auth.users' 18 | table_reference [14..24] 'auth.users' 19 | schema_identifier [14..18] 'auth' 20 | . [18..19] '.' 21 | table_identifier [19..24] 'users' 22 | ; [24..25] ';' 23 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/smallserial.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addSerialColumn 9 | -- Test adding smallserial column to existing table 10 | ALTER TABLE prices ADD COLUMN small_id smallserial; 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/addSerialColumn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Adding a column with type smallserial requires a table rewrite. 18 | 19 | i SERIAL types require rewriting the entire table with an ACCESS EXCLUSIVE lock, blocking all reads and writes. 20 | 21 | i SERIAL types cannot be added to existing tables without a full table rewrite. Consider using a non-serial type with a sequence instead. 22 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addingFieldWithDefault/non_volatile_default.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- Test non-volatile default values (should be safe in PG 11+, but we are passing no PG version info in the tests) 9 | -- expect_lint/safety/addingFieldWithDefault 10 | ALTER TABLE users ADD COLUMN status text DEFAULT 'active'; 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/addingFieldWithDefault ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Adding a column with a DEFAULT value causes a table rewrite. 18 | 19 | i This operation requires an ACCESS EXCLUSIVE lock and rewrites the entire table. 20 | 21 | i Add the column without a default, then set the default in a separate statement. 22 | -------------------------------------------------------------------------------- /crates/pgls_text_edit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_text_edit" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | 14 | [dependencies] 15 | pgls_text_size = { workspace = true } 16 | schemars = { workspace = true, optional = true } 17 | serde = { workspace = true, features = ["derive"], optional = true } 18 | similar = { workspace = true, features = ["unicode"] } 19 | 20 | [features] 21 | schema = ["dep:schemars", "pgls_text_size/schema"] 22 | serde = ["dep:serde", "pgls_text_size/serde"] 23 | 24 | [dev-dependencies] 25 | 26 | [lib] 27 | doctest = false 28 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/create_index_after_alter.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_only_lint/safety/runningStatementWhileHoldingAccessExclusive 9 | -- CREATE INDEX after ALTER TABLE should trigger 10 | ALTER TABLE orders ADD COLUMN total DECIMAL(10, 2); 11 | CREATE INDEX orders_total_idx ON orders(total); 12 | 13 | ``` 14 | 15 | # Diagnostics 16 | lint/safety/runningStatementWhileHoldingAccessExclusive ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17 | 18 | × Running statement while holding ACCESS EXCLUSIVE lock. 19 | 20 | i This blocks all access to the table for the duration of this statement. 21 | 22 | i Run this statement in a separate transaction to minimize lock duration. 23 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/creatingEnum/with_schema.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_only_lint/safety/creatingEnum 9 | -- Enum type with schema qualification 10 | CREATE TYPE myschema.status AS ENUM ('active', 'inactive', 'pending'); 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/creatingEnum ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Creating enum type myschema.status is not recommended. 18 | 19 | i Enum types are difficult to modify: removing values requires complex migrations, and associating additional data with values is not possible. 20 | 21 | i Consider using a lookup table with a foreign key constraint instead, which provides more flexibility and easier maintenance. 22 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/runningStatementWhileHoldingAccessExclusive/insert_after_alter.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_only_lint/safety/runningStatementWhileHoldingAccessExclusive 9 | -- INSERT after ALTER TABLE should trigger 10 | ALTER TABLE books ADD COLUMN isbn TEXT; 11 | INSERT INTO books (title, isbn) VALUES ('Database Systems', '978-0-1234567-8-9'); 12 | 13 | ``` 14 | 15 | # Diagnostics 16 | lint/safety/runningStatementWhileHoldingAccessExclusive ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17 | 18 | × Running statement while holding ACCESS EXCLUSIVE lock. 19 | 20 | i This blocks all access to the table for the duration of this statement. 21 | 22 | i Run this statement in a separate transaction to minimize lock duration. 23 | -------------------------------------------------------------------------------- /crates/pgls_query/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description = "" 5 | edition.workspace = true 6 | homepage.workspace = true 7 | keywords.workspace = true 8 | license.workspace = true 9 | name = "pgls_query" 10 | repository.workspace = true 11 | version = "0.0.0" 12 | 13 | [dependencies] 14 | prost = { workspace = true } 15 | thiserror = { workspace = true } 16 | 17 | pgls_query_macros = { workspace = true } 18 | 19 | 20 | [features] 21 | default = [] 22 | 23 | [build-dependencies] 24 | bindgen = "0.72.0" 25 | cc = "1.0.83" 26 | clippy = { version = "0.0.302", optional = true } 27 | fs_extra = "1.2.0" 28 | glob = "0.3.1" 29 | prost-build = "0.13.5" 30 | which = "6.0.0" 31 | 32 | [dev-dependencies] 33 | easy-parallel = "3.2.0" 34 | -------------------------------------------------------------------------------- /crates/pgls_diagnostics_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro_error::*; 3 | use syn::{DeriveInput, parse_macro_input}; 4 | 5 | mod generate; 6 | mod parse; 7 | 8 | #[proc_macro_derive( 9 | Diagnostic, 10 | attributes( 11 | diagnostic, 12 | severity, 13 | category, 14 | description, 15 | message, 16 | advice, 17 | verbose_advice, 18 | location, 19 | tags, 20 | source 21 | ) 22 | )] 23 | #[proc_macro_error] 24 | pub fn derive_diagnostic(input: TokenStream) -> TokenStream { 25 | let input = parse_macro_input!(input as DeriveInput); 26 | 27 | let input = parse::DeriveInput::parse(input); 28 | 29 | let tokens = generate::generate_diagnostic(input); 30 | 31 | if false { 32 | panic!("{tokens}"); 33 | } 34 | 35 | TokenStream::from(tokens) 36 | } 37 | -------------------------------------------------------------------------------- /crates/pgls_cli/src/execute/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod diagnostics; 3 | mod process_file; 4 | mod stdin; 5 | mod walk; 6 | 7 | pub use config::{ExecutionConfig, ExecutionMode, VcsTargeting}; 8 | 9 | use crate::reporter::Report; 10 | use crate::{CliDiagnostic, CliSession}; 11 | use std::ffi::OsString; 12 | use std::path::PathBuf; 13 | 14 | pub fn run_files( 15 | session: &mut CliSession, 16 | config: &ExecutionConfig, 17 | paths: Vec, 18 | ) -> Result { 19 | walk::traverse(session, config, paths) 20 | } 21 | 22 | pub struct StdinPayload { 23 | #[allow(dead_code)] 24 | pub path: PathBuf, 25 | pub content: String, 26 | } 27 | 28 | pub fn run_stdin( 29 | session: &mut CliSession, 30 | config: &ExecutionConfig, 31 | payload: StdinPayload, 32 | ) -> Result<(), CliDiagnostic> { 33 | stdin::process(session, config, payload) 34 | } 35 | -------------------------------------------------------------------------------- /crates/pgls_analyser/tests/specs/safety/addSerialColumn/generated_stored.sql.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/pgt_analyser/tests/rules_tests.rs 3 | expression: snapshot 4 | snapshot_kind: text 5 | --- 6 | # Input 7 | ``` 8 | -- expect_lint/safety/addSerialColumn 9 | -- Test adding GENERATED ALWAYS AS ... STORED column to existing table 10 | ALTER TABLE prices ADD COLUMN total integer GENERATED ALWAYS AS (price * quantity) STORED; 11 | 12 | ``` 13 | 14 | # Diagnostics 15 | lint/safety/addSerialColumn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16 | 17 | × Adding a column with GENERATED ALWAYS AS ... STORED requires a table rewrite. 18 | 19 | i GENERATED ... STORED columns require rewriting the entire table with an ACCESS EXCLUSIVE lock, blocking all reads and writes. 20 | 21 | i GENERATED ... STORED columns cannot be added to existing tables without a full table rewrite. 22 | --------------------------------------------------------------------------------