branch: externals/sql-indent
commit c70bbf11ba43ce2fd4d6bd9f48f59732c9641244
Author: Alex Harsanyi <[email protected]>
Commit: Alex Harsanyi <[email protected]>
fix syntax detection in exception blocks and case statements (#28 and #29)
Exception statements with multiple when clauses are correctly recognized and
classified. Case statements with multi-lone when clauses are also correctly
identified. Added unit tests for both cases.
Some refactoring was necessary to allow recursive syntax refinement
(`sqlind-refine-syntax`)
---
sql-indent-test.el | 6 +
sql-indent.el | 364 +++++++++++++++++++++++++++----------------------
test-data/pr28-syn.eld | 69 ++++++++++
test-data/pr28.sql | 26 ++++
test-data/pr29-syn.eld | 74 ++++++++++
test-data/pr29.sql | 25 ++++
6 files changed, 404 insertions(+), 160 deletions(-)
diff --git a/sql-indent-test.el b/sql-indent-test.el
index 291a43f..00a2355 100644
--- a/sql-indent-test.el
+++ b/sql-indent-test.el
@@ -302,4 +302,10 @@ information read from DATA-FILE (as generated by
(ert-deftest sqlind-ert-pr24 ()
(sqlind-ert-check-file-syntax "test-data/pr24.sql" "test-data/pr24-syn.eld"))
+(ert-deftest sqlind-ert-pr28 ()
+ (sqlind-ert-check-file-syntax "test-data/pr28.sql" "test-data/pr28-syn.eld"))
+
+(ert-deftest sqlind-ert-pr29 ()
+ (sqlind-ert-check-file-syntax "test-data/pr29.sql" "test-data/pr29-syn.eld"))
+
;;; sql-indent-test.el ends here
diff --git a/sql-indent.el b/sql-indent.el
index 9720a88..e867416 100644
--- a/sql-indent.el
+++ b/sql-indent.el
@@ -135,6 +135,18 @@ whitespace, or at the end of the buffer."
((looking-at "/\\s *$") (goto-char (match-end 0)))
(t (throw 'done (point)))))))
+(defun sqlind-search-backward (start regexp limit)
+ "Search for REGEXP from START backward until LIMIT.
+Finds a match that is not inside a comment or string, moves point
+to the match and returns it. If no match is found, point is moved
+to LIMIT and nil is returned."
+ (goto-char start)
+ (catch 'done
+ (while (re-search-backward regexp limit 'noerror)
+ (unless (sqlind-in-comment-or-string (point))
+ (throw 'done (point))))
+ nil))
+
(defun sqlind-match-string (pos)
"Return the match data at POS in the current buffer.
This is similar to `match-data', but the text is fetched without
@@ -410,12 +422,8 @@ See also `sqlind-beginning-of-block'"
(setq case-label
(if case-label (substring case-label 2 -2) ""))
(if (null sqlind-end-stmt-stack)
- (let ((limit (point)))
- (goto-char start-pos)
- (while (re-search-backward "\\_<when\\_>" limit 'noerror)
- (unless (sqlind-in-comment-or-string (point))
- (throw 'finished
- (list 'in-block 'case case-label)))))
+ (when (sqlind-search-backward start-pos "\\_<when\\_>"
(point))
+ (throw 'finished (list 'in-block 'case case-label)))
;; else
(cl-destructuring-bind (pos kind label)
(pop sqlind-end-stmt-stack)
@@ -671,10 +679,17 @@ See also `sqlind-beginning-of-block'"
(throw 'finished
(list 'syntax-error "bad end label for defun" (point)
pos)))))))))
+(defun sqlind-maybe-exception-statement ()
+ "If (point) is on an exception keyword, report its syntax.
+See also `sqlind-beginning-of-block'"
+ (when (and (looking-at "exception")
+ (null sqlind-end-stmt-stack))
+ (throw 'finished (list 'in-block 'exception))))
+
(defconst sqlind-start-block-regexp
(concat "\\(\\b"
(regexp-opt '("if" "then" "else" "elsif" "loop"
- "begin" "declare" "create"
+ "begin" "declare" "create" "exception"
"procedure" "function" "end" "case") t)
"\\b\\)\\|)")
"Regexp to match the start of a block.")
@@ -694,6 +709,7 @@ reverse order (a stack) and is used to skip over nested
blocks."
(sqlind-maybe-if-statement)
(sqlind-maybe-case-statement)
(sqlind-maybe-then-statement)
+ (sqlind-maybe-exception-statement)
(sqlind-maybe-else-statement)
(sqlind-maybe-loop-statement)
(sqlind-maybe-begin-statement)
@@ -831,10 +847,9 @@ reverse order (a stack) and is used to skip over nested
blocks."
(when (or (looking-at "on")
(progn (forward-word -1) (looking-at "on")))
;; look for the join start, that will be the anchor
- (while (re-search-backward sqlind-select-join-regexp start t)
- (unless (sqlind-in-comment-or-string (point))
- (throw 'finished
- (cons 'select-join-condition (point))))))
+ (when (sqlind-search-backward (point)
sqlind-select-join-regexp start)
+ (throw 'finished
+ (cons 'select-join-condition (point))))))
;; if this line starts with a ',' or the previous
;; line starts with a ',', we have a new table
@@ -847,7 +862,7 @@ reverse order (a stack) and is used to skip over nested
blocks."
;; otherwise, we continue the table definition from
;; the previous line.
- (throw 'finished (cons 'select-table-continuation match-pos))))
+ (throw 'finished (cons 'select-table-continuation match-pos)))
(t
(throw 'finished
@@ -1018,7 +1033,6 @@ KIND is the symbol determining the type of the block
('if, 'loop,
(cond
((eq block-kind 'exception)
(goto-char anchor)
- (forward-line -1)
(throw 'done
(sqlind-refine-end-syntax
end-kind end-label end-pos (sqlind-syntax-of-line))))
@@ -1122,6 +1136,182 @@ KIND is the symbol determining the type of the block
('if, 'loop,
;;;;; sqlind-syntax-of-line
+(defun sqlind-refine-syntax (context pos have-block-context)
+ "Refine a basic syntax CONTEXT at POS.
+CONTEXT is a syntactic context obtained by looking at the
+statement start and block start, see `sqlind-syntax-of-line'. We
+refine it by looking at the contents of the current line and the
+contents of the anchor.
+
+HAVE-BLOCK-CONTEXT indicates that we are indenting a statement,
+not a statement-continuation POS is the same as the
+`sqlind-beginning-of-statement'."
+ (let ((syntax (sqlind-syntax context))
+ (anchor (sqlind-anchor-point context))
+ (syntax-symbol (sqlind-syntax-symbol context)))
+
+ (goto-char pos)
+
+ (cond
+ ;; do we start a comment?
+ ((and (not (eq syntax-symbol 'comment-continuation))
+ (looking-at sqlind-comment-start-skip))
+ (push (cons 'comment-start anchor) context))
+
+ ;; Refine a statement continuation
+ ((memq syntax-symbol '(statement-continuation
nested-statement-continuation))
+
+ ;; a (nested) statement continuation which starts with loop
+ ;; or then is a block start
+ (if (and have-block-context (looking-at "\\(loop\\|then\\|when\\)\\_>"))
+ (push (cons (list 'block-start (intern (sqlind-match-string 0)))
anchor)
+ context)
+ ;; else
+ (goto-char anchor)
+ (when (eq syntax 'nested-statement-continuation)
+ (forward-char 1)
+ (skip-chars-forward " \t\r\n\f\v")
+ (setq anchor (point)))
+
+ ;; when all we have before `pos' is a label, we have a
+ ;; labeled-statement-start
+ (when (looking-at "<<\\([a-z0-9_]+\\)>>")
+ (goto-char (match-end 0))
+ (forward-char 1)
+ (sqlind-forward-syntactic-ws)
+ (when (eq (point) pos)
+ (push (cons 'labeled-statement-start anchor) context)))
+
+ (when (looking-at "when\\_>")
+ (let* ((acontext (sqlind-syntax-of-line))
+ (asyntax (sqlind-syntax acontext)))
+ (cond ((equal asyntax '(in-block exception ""))
+ (push (cons '(in-block exception-handler "") (point))
context))
+ ((equal asyntax '(block-start when))
+ ;; Refine again in the context of the when line
+ (setq context (sqlind-refine-syntax (cdr acontext) pos
have-block-context))))))
+
+ ;; maybe we have a DML statement (select, insert, update and
+ ;; delete)
+
+ ;; skip a cursor definition if it is before our point, in the
+ ;; following format:
+ ;;
+ ;; CURSOR name IS
+ ;; CURSOR name type IS
+ (when (looking-at "cursor\\b")
+ (let ((origin (point)))
+ (forward-sexp 3)
+ (sqlind-forward-syntactic-ws)
+ (when (looking-at "is\\b")
+ (goto-char (match-end 0))
+ (sqlind-forward-syntactic-ws))
+ (unless (<= (point) pos)
+ (goto-char origin))))
+
+ ;; skip a forall statement if it is before our point
+ (when (looking-at "forall\\b")
+ (when (re-search-forward
"\\b\\(select\\|update\\|delete\\|insert\\)\\b" pos 'noerror)
+ (goto-char (match-beginning 0))))
+
+ ;; only check for syntax inside DML clauses if we are not
+ ;; at the start of one.
+ (when (< (point) pos)
+ (cond
+ ;; NOTE: We only catch here "CASE" expressions, not CASE
+ ;; statements. We also catch assignments with case (var
+ ;; := CASE ...)
+ ((looking-at "\\(\\w+[ \t\r\n\f]+:=[ \t\r\n\f]+\\)?\\(case\\)")
+ (when (< (match-beginning 2) pos)
+ (push (sqlind-syntax-in-case pos (match-beginning 2))
context)))
+ ((looking-at "with")
+ (push (sqlind-syntax-in-with pos (point)) context))
+ ((looking-at "select")
+ (push (sqlind-syntax-in-select pos (point)) context))
+ ((looking-at "insert")
+ (push (sqlind-syntax-in-insert pos (point)) context))
+ ((looking-at "delete")
+ (push (sqlind-syntax-in-delete pos (point)) context))
+ ((looking-at "update")
+ (push (sqlind-syntax-in-update pos (point)) context))))
+
+ (when (eq (sqlind-syntax-symbol context) 'select-column-continuation)
+ (let ((cdef (sqlind-column-definition-start pos
(sqlind-anchor-point context))))
+ (when cdef
+ (save-excursion
+ (goto-char cdef)
+ (when (looking-at "case")
+ (push (sqlind-syntax-in-case pos (point)) context))))))
+
+ ))
+
+ ;; create block start syntax if needed
+
+ ((and (eq syntax-symbol 'in-block)
+ (memq (nth 1 syntax) '(if elsif then case))
+ (looking-at "\\(then\\|\\(els\\(e\\|if\\)\\)\\)\\_>"))
+ (let ((what (intern (sqlind-match-string 0))))
+ ;; the only invalid combination is a then statement in
+ ;; an (in-block "then") context
+ (unless (and (eq what 'then) (equal (nth 1 syntax) 'then))
+ (push (cons (list 'block-start what) anchor) context))))
+
+ ((and (eq syntax-symbol 'in-block)
+ (eq (nth 1 syntax) 'exception)
+ (not (looking-at "when\\_>")))
+ (save-excursion
+ (when (sqlind-search-backward pos "when\\_>" anchor)
+ (push (cons (list 'in-block 'exception-handler) (point)) context))))
+
+ ;; note that begin is not a block-start in a 'in-begin-block
+ ;; context
+ ((and (memq syntax-symbol '(defun-start declare-statement toplevel
+ package package-body))
+ (looking-at "begin\\_>"))
+ (push (cons (list 'block-start 'begin) anchor) context))
+
+ ((and (memq syntax-symbol '(defun-start package package-body))
+ (looking-at "\\(is\\|as\\)\\_>"))
+ (push (cons (list 'block-start 'is-or-as) anchor) context))
+
+ ((and (memq syntax-symbol '(in-begin-block in-block))
+ (looking-at "exception\\_>"))
+ (push (cons (list 'block-start 'exception) anchor) context))
+
+ ((and (eq syntax-symbol 'in-block)
+ (memq (nth 1 syntax) '(then case)))
+ (if (looking-at "when\\_>")
+ (push (cons (list 'block-start 'when) anchor) context)
+ ;; NOTE: the "when" case is handed above
+ (when (sqlind-search-backward pos "when\\_>" anchor)
+ (push (cons '(in-block when) (point)) context))))
+
+ ;; indenting the select clause inside a view
+ ((and (eq syntax-symbol 'create-statement)
+ (eq (nth 1 syntax) 'view))
+ (goto-char anchor)
+ (catch 'done
+ (while (re-search-forward "\\bselect\\b" pos 'noerror)
+ (goto-char (match-beginning 0))
+ (when (sqlind-same-level-statement (point) anchor)
+ (push (sqlind-syntax-in-select pos (point)) context)
+ (throw 'done nil))
+ (goto-char (match-end 0)))))
+
+ ;; create a block-end syntax if needed
+
+ ((and (not (eq syntax-symbol 'comment-continuation))
+ (looking-at "end[ \t\r\n\f]*\\(\\_<\\(?:if\\|loop\\|case\\)\\_>\\)?[
\t\r\n\f]*\\(\\_<\\(?:[a-z0-9_]+\\)\\_>\\)?"))
+ ;; so we see the syntax which closes the current block. We still
+ ;; need to check if the current end is a valid closing block
+ (let ((kind (or (sqlind-match-string 1) ""))
+ (label (or (sqlind-match-string 2) "")))
+ (push (sqlind-refine-end-syntax
+ (if (equal kind "") nil (intern kind))
+ label (point) context)
+ context))))
+ context))
+
(defun sqlind-syntax-of-line ()
"Return the syntax at the start of the current line.
The function returns a list of (SYNTAX . ANCHOR) cons cells.
@@ -1185,154 +1375,8 @@ procedure block."
;; now let's refine the syntax by adding info about the current line
;; into the mix.
+ (sqlind-refine-syntax context pos have-block-context)))))
- (let ((syntax (sqlind-syntax context))
- (anchor (sqlind-anchor-point context))
- (syntax-symbol (sqlind-syntax-symbol context)))
-
- (goto-char pos)
-
- (cond
- ;; do we start a comment?
- ((and (not (eq syntax-symbol 'comment-continuation))
- (looking-at sqlind-comment-start-skip))
- (push (cons 'comment-start anchor) context))
-
- ;; Refine a statement continuation
- ((memq syntax-symbol '(statement-continuation
nested-statement-continuation))
-
- ;; a (nested) statement continuation which starts with loop
- ;; or then is a block start
- (if (and have-block-context (looking-at
"\\(loop\\|then\\|when\\)\\_>"))
- (push (cons (list 'block-start (intern (sqlind-match-string
0))) anchor)
- context)
- ;; else
- (goto-char anchor)
- (when (eq syntax 'nested-statement-continuation)
- (forward-char 1)
- (skip-chars-forward " \t\r\n\f\v")
- (setq anchor (point)))
-
- ;; when all we have before `pos' is a label, we have a
- ;; labeled-statement-start
- (if (looking-at "<<\\([a-z0-9_]+\\)>>")
- (progn
- (goto-char (match-end 0))
- (forward-char 1)
- (sqlind-forward-syntactic-ws)
- (when (eq (point) pos)
- (push (cons 'labeled-statement-start anchor) context)))
-
- ;; else, maybe we have a DML statement (select, insert,
- ;; update and delete)
-
- ;; skip a cursor definition if it is before our point, in the
- ;; following format:
- ;;
- ;; CURSOR name IS
- ;; CURSOR name type IS
- (when (looking-at "cursor\\b")
- (let ((origin (point)))
- (forward-sexp 3)
- (sqlind-forward-syntactic-ws)
- (when (looking-at "is\\b")
- (goto-char (match-end 0))
- (sqlind-forward-syntactic-ws))
- (unless (<= (point) pos)
- (goto-char origin))))
-
- ;; skip a forall statement if it is before our point
- (when (looking-at "forall\\b")
- (when (re-search-forward
"\\b\\(select\\|update\\|delete\\|insert\\)\\b" pos 'noerror)
- (goto-char (match-beginning 0))))
-
- ;; only check for syntax inside DML clauses if we are not
- ;; at the start of one.
- (when (< (point) pos)
- (cond
- ;; NOTE: We only catch here "CASE" expressions, not CASE
- ;; statements. We also catch assignments with case (var
- ;; := CASE ...)
- ((looking-at "\\(\\w+[ \t\r\n\f]+:=[
\t\r\n\f]+\\)?\\(case\\)")
- (when (< (match-beginning 2) pos)
- (push (sqlind-syntax-in-case pos (match-beginning 2))
context)))
- ((looking-at "with")
- (push (sqlind-syntax-in-with pos (point)) context))
- ((looking-at "select")
- (push (sqlind-syntax-in-select pos (point)) context))
- ((looking-at "insert")
- (push (sqlind-syntax-in-insert pos (point)) context))
- ((looking-at "delete")
- (push (sqlind-syntax-in-delete pos (point)) context))
- ((looking-at "update")
- (push (sqlind-syntax-in-update pos (point)) context))))
-
- (when (eq (sqlind-syntax-symbol context)
'select-column-continuation)
- (let ((cdef (sqlind-column-definition-start pos
(sqlind-anchor-point context))))
- (when cdef
- (save-excursion
- (goto-char cdef)
- (when (looking-at "case")
- (push (sqlind-syntax-in-case pos (point))
context))))))
-
- )))
-
- ;; create block start syntax if needed
-
- ((and (eq syntax-symbol 'in-block)
- (memq (nth 1 syntax) '(if elsif then case))
- (looking-at "\\(then\\|\\(els\\(e\\|if\\)\\)\\)\\_>"))
- (let ((what (intern (sqlind-match-string 0))))
- ;; the only invalid combination is a then statement in
- ;; an (in-block "then") context
- (unless (and (eq what 'then) (equal (nth 1 syntax) 'then))
- (push (cons (list 'block-start what) anchor) context))))
-
- ;; note that begin is not a block-start in a 'in-begin-block
- ;; context
- ((and (memq syntax-symbol '(defun-start declare-statement toplevel
- package package-body))
- (looking-at "begin\\_>"))
- (push (cons (list 'block-start 'begin) anchor) context))
-
- ((and (memq syntax-symbol '(defun-start package package-body))
- (looking-at "\\(is\\|as\\)\\_>"))
- (push (cons (list 'block-start 'is-or-as) anchor) context))
-
- ((and (memq syntax-symbol '(in-begin-block in-block))
- (looking-at "exception\\_>"))
- (push (cons (list 'block-start 'exception) anchor) context))
-
- ((and (eq syntax-symbol 'in-block)
- (memq (nth 1 syntax) '(then case))
- (looking-at "when\\_>"))
- (push (cons (list 'block-start 'when) anchor) context))
-
- ;; indenting the select clause inside a view
- ((and (eq syntax-symbol 'create-statement)
- (eq (nth 1 syntax) 'view))
- (goto-char anchor)
- (catch 'done
- (while (re-search-forward "\\bselect\\b" pos 'noerror)
- (goto-char (match-beginning 0))
- (when (sqlind-same-level-statement (point) anchor)
- (push (sqlind-syntax-in-select pos (point)) context)
- (throw 'done nil))
- (goto-char (match-end 0)))))
-
- ;; create a block-end syntax if needed
-
- ((and (not (eq syntax-symbol 'comment-continuation))
- (looking-at "end[
\t\r\n\f]*\\(\\_<\\(?:if\\|loop\\|case\\)\\_>\\)?[
\t\r\n\f]*\\(\\_<\\(?:[a-z0-9_]+\\)\\_>\\)?"))
- ;; so we see the syntax which closes the current block. We still
- ;; need to check if the current end is a valid closing block
- (let ((kind (or (sqlind-match-string 1) ""))
- (label (or (sqlind-match-string 2) "")))
- (push (sqlind-refine-end-syntax
- (if (equal kind "") nil (intern kind))
- label (point) context)
- context)))))
- context))))
(defun sqlind-show-syntax-of-line ()
"Print the syntax of the current line."
diff --git a/test-data/pr28-syn.eld b/test-data/pr28-syn.eld
new file mode 100644
index 0000000..5ac3da2
--- /dev/null
+++ b/test-data/pr28-syn.eld
@@ -0,0 +1,69 @@
+(((toplevel . 1))
+ ((declare-statement . 1))
+ (((block-start begin)
+ . 1)
+ (declare-statement . 1))
+ (((in-begin-block toplevel-block "")
+ . 25))
+ (((in-begin-block nil "")
+ . 33))
+ (((in-block if "")
+ . 43))
+ (((in-block if "")
+ . 43))
+ (((block-start else)
+ . 43)
+ ((in-block if "")
+ . 43))
+ (((in-block else "")
+ . 87))
+ (((in-block else "")
+ . 87))
+ (((block-end if "")
+ . 43)
+ ((in-block else "")
+ . 87))
+ (((block-start exception)
+ . 33)
+ ((in-begin-block nil "")
+ . 33))
+ (((in-block exception)
+ . 132))
+ (((in-block exception-handler)
+ . 146)
+ ((in-block exception "")
+ . 132))
+ (((in-block exception-handler)
+ . 146)
+ ((in-block exception "")
+ . 132))
+ (((in-block exception "")
+ . 132))
+ (((in-block exception-handler "")
+ . 200)
+ (statement-continuation . 200))
+ (((in-block exception-handler)
+ . 200)
+ ((in-block exception "")
+ . 132))
+ (((in-block exception "")
+ . 132))
+ (((in-block exception-handler "")
+ . 254)
+ (statement-continuation . 254))
+ (((in-block exception-handler)
+ . 254)
+ ((in-block exception "")
+ . 132))
+ (((block-end toplevel-block "")
+ . 25)
+ ((in-begin-block toplevel-block "")
+ . 25))
+ ((toplevel . 1))
+ ((comment-start . 1)
+ (toplevel . 1))
+ ((comment-start . 1)
+ (toplevel . 1))
+ ((comment-start . 1)
+ (toplevel . 1))
+ ((toplevel . 1)))
diff --git a/test-data/pr28.sql b/test-data/pr28.sql
new file mode 100644
index 0000000..cf8b88c
--- /dev/null
+++ b/test-data/pr28.sql
@@ -0,0 +1,26 @@
+declare
+ dummy number;
+begin
+ begin
+ if 1 = 1 then
+ proc1;
+ proc2;
+ else
+ proc3;
+ proc4;
+ end if;
+ exception
+ when no_data_found then
+ proc1;
+ proc2;
+ when too_many_rows then
+ proc3;
+ proc4;
+ when others then
+ proc5;
+ end;
+end;
+/
+-- Local Variables:
+-- sql-product: oracle
+-- End:
diff --git a/test-data/pr29-syn.eld b/test-data/pr29-syn.eld
new file mode 100644
index 0000000..a206aee
--- /dev/null
+++ b/test-data/pr29-syn.eld
@@ -0,0 +1,74 @@
+(((toplevel . 1))
+ ((select-column . 1)
+ (statement-continuation . 1))
+ ((case-clause . 19)
+ (select-column-continuation . 1)
+ (statement-continuation . 1))
+ ((case-clause . 19)
+ (select-column-continuation . 1)
+ (statement-continuation . 1))
+ ((case-clause . 19)
+ (select-column-continuation . 1)
+ (statement-continuation . 1))
+ (((block-end case "")
+ . 19)
+ (select-column-continuation . 1)
+ (statement-continuation . 1))
+ ((select-clause . 1)
+ (statement-continuation . 1))
+ ((toplevel . 1))
+ ((toplevel . 1))
+ ((declare-statement . 141))
+ (((block-start begin)
+ . 141)
+ (declare-statement . 141))
+ (((in-begin-block toplevel-block "")
+ . 165))
+ (((block-start when)
+ . 173)
+ ((in-block case "")
+ . 173))
+ (((in-block when)
+ . 184)
+ ((in-block case "")
+ . 184))
+ (((in-block when)
+ . 184)
+ ((in-block case "")
+ . 184))
+ (((block-start when)
+ . 184)
+ ((in-block case "")
+ . 184))
+ (((in-block when)
+ . 239)
+ ((in-block case "")
+ . 184))
+ (((in-block when)
+ . 239)
+ ((in-block case "")
+ . 184))
+ (((block-start when)
+ . 184)
+ ((in-block case "")
+ . 184))
+ (((in-block when)
+ . 294)
+ ((in-block case "")
+ . 184))
+ (((block-start else)
+ . 184)
+ ((in-block case "")
+ . 184))
+ (((in-block else "")
+ . 329))
+ (((block-end case "")
+ . 184)
+ ((in-block else "")
+ . 329))
+ (((block-end toplevel-block "")
+ . 165)
+ ((in-begin-block toplevel-block "")
+ . 165))
+ ((toplevel . 1))
+ ((toplevel . 1)))
\ No newline at end of file
diff --git a/test-data/pr29.sql b/test-data/pr29.sql
new file mode 100644
index 0000000..6f5a453
--- /dev/null
+++ b/test-data/pr29.sql
@@ -0,0 +1,25 @@
+select y ,
+ case
+ when foo>5 then "great"
+ when foo=5 then "normal"
+ else "poor"
+ end as level
+from bar;
+
+declare
+ dummy number;
+begin
+ case ind
+ when 1 then
+ dummy := 'Guy';
+ dummy1 := 'Abc';
+ when 2 then
+ dummy := 'Abc';
+ dummy1 := 'Abc';
+ when 3 then
+ dummy := 'Def';
+ else
+ dummy := 'World';
+ end case;
+end;
+/