Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,11 @@ Actual colors depend on your theme. The table below documents the TextMate scope
- 100+ PostgreSQL type names with multi-word support
- 200+ built-in functions and support constants
- Standalone keyword fallbacks for multiline resilience
- 415 test assertions across 20 test files

## Usage

```bash
npm test # run 415 grammar tests
npm test # run grammar tests
npm run validate # 0 unscoped tokens across all samples
npm run validate-sql # all statements parse via libpg-query
npm run preview # http://localhost:3117 — live preview with theme picker
Expand Down
14 changes: 10 additions & 4 deletions pgsql.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,13 @@
"include": "#support_constants"
},
{
"include": "#keywords_all"
"include": "#keyword_dml"
},
{
"include": "#keyword_control"
},
{
"include": "#keyword_other"
},
{
"include": "#language_constants"
Expand Down Expand Up @@ -874,7 +880,7 @@
"patterns": [
{
"match": "(?xi)\\b(century|day|decade|dow|doy|epoch|hour|isodow|isoyear|julian|microseconds|millennium|milliseconds|minute|month|quarter|second|timezone_hour|timezone_minute|timezone|week|year)\\b",
"name": "entity.name.label.pgsql"
"name": "constant.language.pgsql"
},
{
"include": "#paren_group"
Expand Down Expand Up @@ -1088,7 +1094,7 @@
]
},
"keyword_dml": {
"match": "(?xi)\\b(default\\s+values|on\\s+conflict|do\\s+nothing|do\\s+update|select|insert|update|delete|merge|returning|truncate|copy|values|set|matched|conflict)\\b",
"match": "(?xi)\\b(default\\s+values|on\\s+conflict|do\\s+nothing|do\\s+update|select|insert|update|delete|merge|returning|truncate|copy|values|set|matched|conflict|cascade|restrict)\\b",
"name": "keyword.dml.pgsql"
},
"keyword_ddl": {
Expand All @@ -1105,7 +1111,7 @@
},
"language_constants": {
"match": "(?xi)\\b(null|true|false|unknown)\\b",
"name": "entity.name.label.pgsql"
"name": "constant.language.pgsql"
},
"function_call": {
"comment": "Any identifier followed by ( — catches user-defined function calls",
Expand Down
16 changes: 15 additions & 1 deletion samples/01-dml.sql
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ WHEN NOT MATCHED BY TARGET THEN
WHEN NOT MATCHED BY SOURCE THEN
DELETE;

-- TRUNCATE with RESTART IDENTITY
-- TRUNCATE with CASCADE / RESTRICT
TRUNCATE TABLE old_logs, archived_sessions;
TRUNCATE TABLE counters RESTART IDENTITY CASCADE;
TRUNCATE TABLE staging_data RESTRICT;

-- COPY variants
COPY users (name, email) TO '/tmp/users.csv' WITH (FORMAT csv, HEADER true, DELIMITER ',', QUOTE '"', ESCAPE '\');
Expand Down Expand Up @@ -289,6 +290,19 @@ SELECT * FROM accounts WHERE id = 1 FOR UPDATE OF accounts;
SELECT * FROM users WHERE email ISNULL;
SELECT * FROM users WHERE email NOTNULL;

-- NULL / TRUE / FALSE as language constants
UPDATE accounts SET worker_id = NULL, claimed_at = NULL WHERE id = 1;
SELECT * FROM users WHERE is_active = TRUE AND is_deleted = FALSE;

-- Keyword-named functions (is, similar etc.) used as function calls
-- 'is' before '(' is a function call (e.g. pgTAP), not the IS keyword
SELECT is(get_count(), 0, 'should be zero');
SELECT is(
(SELECT was_cancelled FROM cancel_run(42)),
FALSE,
'cancel of completed run returns false'
);

-- Quoted identifiers
SELECT "user"."first_name", "user"."last_name"
FROM "public"."user"
Expand Down
35 changes: 35 additions & 0 deletions samples/05-plpgsql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,41 @@ BEGIN
END;
$$;

-- NULL/TRUE/FALSE as language constants inside PL/pgSQL body
CREATE OR REPLACE FUNCTION reset_run(p_run_id BIGINT)
RETURNS VOID LANGUAGE plpgsql AS $$
BEGIN
UPDATE wf.runs
SET status = 'queued',
worker_id = NULL,
claimed_at = NULL,
timeout_at = NULL,
finished_at = NULL
WHERE id = p_run_id;

IF NOT FOUND THEN
RAISE WARNING 'run % not found', p_run_id;
END IF;
END;
$$;

-- Column named 'output' in INSERT inside a function body
-- (should NOT be highlighted as a DDL keyword)
CREATE OR REPLACE FUNCTION complete_run(p_run_id BIGINT, p_output JSONB)
RETURNS VOID LANGUAGE plpgsql AS $$
BEGIN
UPDATE wf.runs
SET status = 'completed',
output = p_output,
worker_id = NULL,
finished_at = now()
WHERE id = p_run_id;

INSERT INTO wf.ev_run_completed (run_id, output)
VALUES (p_run_id, p_output);
END;
$$;

-- Trigger function with NEW/OLD references
CREATE OR REPLACE FUNCTION audit_trigger()
RETURNS TRIGGER
Expand Down
40 changes: 34 additions & 6 deletions tests/constants.test.sql
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
-- Boolean and null constants
-- Boolean and null constants in a SELECT
SELECT NULL, TRUE, FALSE;
-- ^^^^ entity.name.label
-- ^^^^ entity.name.label
-- ^^^^^ entity.name.label
-- ^^^^ constant.language
-- ^^^^ constant.language
-- ^^^^^ constant.language

-- UNKNOWN constant (three-valued logic)
SELECT UNKNOWN;
-- ^^^^^^^ entity.name.label
-- ^^^^^^^ constant.language

-- NULL in UPDATE SET (DML context)
UPDATE t SET x = NULL, y = TRUE, z = FALSE WHERE id = 1;
-- ^^^^ constant.language
-- ^^^^ constant.language
-- ^^^^^ constant.language

-- NULL should NOT be highlighted as a keyword
UPDATE t SET x = NULL WHERE id = 1;
-- ^^^^ !keyword

-- NULL in PL/pgSQL body (dollar_quotes context)
DO $$ BEGIN UPDATE t SET x = NULL WHERE id = 1; END; $$;
-- ^^^^ constant.language

-- TRUE/FALSE in IF condition (dollar_quotes context)
DO $$ BEGIN IF TRUE THEN RETURN NULL; END IF; END; $$;
-- ^^^^ constant.language
-- ^^^^ constant.language

-- FALSE/TRUE as function arguments in DML
SELECT coalesce(FALSE, TRUE);
-- ^^^^^ constant.language
-- ^^^^ constant.language

-- EXTRACT field names
SELECT extract(EPOCH FROM now());
-- ^^^^^ entity.name.label
-- ^^^^^ constant.language

SELECT extract(year FROM hire_date), extract(month FROM hire_date);
-- ^^^^ constant.language
-- ^^^^^ constant.language
10 changes: 10 additions & 0 deletions tests/dml-keywords.test.sql
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,13 @@ COPY t FROM STDIN WITH (ON_ERROR stop, LOG_VERBOSITY verbose);
-- BINARY option
COPY t TO STDOUT WITH (FORMAT BINARY);
-- ^^^^^^ keyword

-- CASCADE and RESTRICT in TRUNCATE (DML context)
TRUNCATE TABLE t CASCADE;
-- ^^^^^^^ keyword.dml
TRUNCATE TABLE t RESTRICT;
-- ^^^^^^^^ keyword.dml

-- CASCADE should NOT be unstyled in TRUNCATE
TRUNCATE TABLE t CASCADE;
-- ^^^^^^^ !keyword.ddl
10 changes: 9 additions & 1 deletion tests/functions.test.sql
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,13 @@ INSERT INTO users (name) VALUES (1);
-- ^^^^^ !support.function

-- Table names after REFERENCES should NOT be functions
REFERENCES users(id)
REFERENCES users(id);
-- ^^^^^ !support.function

-- 'is' as SQL keyword (not followed by '(') should remain a keyword
SELECT x FROM t WHERE x IS NULL;
-- ^^ keyword

-- column named 'output' in INSERT inside dollar-quote should NOT be keyword.ddl
DO $$ BEGIN INSERT INTO t (id, output) VALUES (1, 'x'); END; $$;
-- ^^^^^^ !keyword.ddl
12 changes: 6 additions & 6 deletions tests/is-constructs.test.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,29 @@ SELECT x FROM t WHERE x IS NOT NULL;
-- IS TRUE / IS NOT TRUE
SELECT x FROM t WHERE b IS TRUE;
-- ^^ keyword
-- ^^^^ entity.name.label
-- ^^^^ constant.language
SELECT x FROM t WHERE b IS NOT TRUE;
-- ^^ keyword
-- ^^^ keyword
-- ^^^^ entity.name.label
-- ^^^^ constant.language

-- IS FALSE / IS NOT FALSE
SELECT x FROM t WHERE b IS FALSE;
-- ^^ keyword
-- ^^^^^ entity.name.label
-- ^^^^^ constant.language
SELECT x FROM t WHERE b IS NOT FALSE;
-- ^^ keyword
-- ^^^ keyword
-- ^^^^^ entity.name.label
-- ^^^^^ constant.language

-- IS UNKNOWN / IS NOT UNKNOWN
SELECT x FROM t WHERE b IS UNKNOWN;
-- ^^ keyword
-- ^^^^^^^ entity.name.label
-- ^^^^^^^ constant.language
SELECT x FROM t WHERE b IS NOT UNKNOWN;
-- ^^ keyword
-- ^^^ keyword
-- ^^^^^^^ entity.name.label
-- ^^^^^^^ constant.language

-- IS DISTINCT FROM / IS NOT DISTINCT FROM
SELECT x FROM t WHERE a IS DISTINCT FROM b;
Expand Down
Loading