Custom Formatting in SQLDev 4.2
January 3, 2017
Formatting is one of the software development features that always steers controversy. To put it bluntly, everybody wants to format the code their own way. In previous SQL Dev versions the answer to that challenge was having elaborate tree-table widget spiced with several dozens of various options.
Arguably, the combined SQL and PL/SQL grammar is the most cumbersome programming language in existence, with total number of grammar productions exceeding 20000. From that perspective, the list of “Advanced Format” options offered by SQL Dev feels inadequate. To address this mismatch, the latest release introduced “Custom Format”. It offers to a developer means to specify formal conditions which SQL and PL/SQL grammatical constructs to indent, align, pad, add extra line breaks and so on.
As usual, the best way to demonstrate a new feature is an example. Consider the following enhancement request by Stefan Poschenrieder to remove the indent of the NOT NULL ENABLE inline constraint in the following code
CREATE TABLE dim_aspect ( dim_aspectid NUMBER(11,0) NOT NULL ENABLE, source VARCHAR2(50), hier1_desc VARCHAR2(50), hierarchy1 VARCHAR2(50), dwh_inserteddate DATE );
The first step is identifying the target grammar symbol in the Code Outline panel. Here is screen snapshot illustrating it:
As illustrated there, positioning a cursor over the NOT keyword highlights the corresponding node in the parse tree. Therefore, the grammar symbol of interest is probably “inline_constraint“. Clicking onto the node labeled inline_constraint to double check this guess witnesses it highlighting “NOT NULL ENABLE” in the code editor. (Please be aware that clicking onto a hyperlinked looking grammar symbol has side effect of opening documentation page featuring that symbol railroad diagram definition).
As soon as the grammar symbol of interest is known, lets find it at Custom Format preferences panel. Admittedly, glaring omission there is the search widget. Yet, grammar symbols are arranged in lexicographic order, so that a reader would have no trouble spotting inline_constraint in the following formatting rule:
... | [node) index_subpartition_clause[69,119)# | [node) inline_constraint & ![node) inline_constraint[14,67) | [node) inline_ref_constraint ...
This fragment of rather convoluted disjunctive condition instructs the formatter to indent the nodes with payload “index_subpartition_clause[69,119)#“, or the nodes labeled with “inline_constraint” (but not those that also labeled as “inline_constraint[14,67)“), or the nodes labeled “inline_ref_constraint“. Therefore, commenting out the middle conjunction
... | [node) index_subpartition_clause[69,119)# --| [node) inline_constraint & ![node) inline_constraint[14,67) | [node) inline_ref_constraint ...
is likely a solution to the problem. After making this change, a reader is advised to test what effect do modified formatting rules have on a SQL or PL/SQL code sample in the preview editor. This action is performed with the bottom-left “Run” button. The two neighbors “Import…” and “Export…” are for saving and retrieving your work. By default the modified “format.arbori” file is kept in the SQLDev product preferences directory.
Let’s reinforce this custom formatting idea with two more examples, both from Steven Feuerstein. In both cases we’ll learn some additional syntax of that formal formatting rules specification. The first example is just a bug witnessed by the following test case:
CREATE OR REPLACE PACKAGE BODY plch_pkg IS BEGIN FOR indx IN 1..100000 LOOP g_strings( indx --<--- ??? ) := 'String ' || indx; END LOOP; END; /
The rule responsible for that unwanted indentation of the “indx” parameter is
| [node) pls_expr & [node-1) '(' & ![node) numeric_literal
It requires the parse node to be labeled with “pls_expr” but not “numeric_literal“. Also, it stipulates that the prior sibling node (formally, “node-1“) is labeled with open parenthesis. Adding the condition for the node successor not to be the closing parenthesis
| [node) pls_expr & [node-1) '(' & ![node) numeric_literal & ![node+1) ')'
is all it takes to fix this snag.
In the second example
CREATE PACKAGE fmt AS PROCEDURE create_checklist ( user_id_in IN INTEGER, question_id_in IN INTEGER ); --<-- wanted extra line break PROCEDURE remove_checklist ( checklist_id_in IN INTEGER ); PROCEDURE remove_checklist ( user_id_in IN INTEGER, question_id_in IN INTEGER ); END fmt;
the formatted code have only single line breaks after each package member. The formatting rule responsible for this functionality is “extraLines“. It formally a disjunction of the two rules “sql_stmts” and “significant_statements“. In the later rule we see the following conjunctive condition:
| [node) basic_decl_item & [node+20 < node)
The syntax of first part is familiar: it requires the node to be labeled as “basic_decl_item“.
The second part is little more cumbersome. The “[ node” and “node )” refer to the node beginning and ending position, correspondingly. Each node in the parse tree recognizes a sequence of tokens. In our example nodes labeled as “basic_decl_item” recognizes a sequence of tokens beginning with the PROCEDURE keyword and ending with semicolon. The beginning position is the offset of the PROCEDURE keyword, while the ending position is the offset of the semicolon. The condition “[node+20 < node)” requires more than 20 tokens between the node beginning and ending position, or informally the package member to be of “sufficient length”.
Please note that package data members are recognized as “basic_decl_item” as well. Therefore, the proper fix depends on if a user wants to inset double line breaks after data members or not. The length condition was a naive attempt on distinguishing data and procedures, but more elaborate condition, distinguishing data and procedures is possible too.
An adventurous reader is encouraged to experiment with the formatting rules in the Arbori query panel that I have described earlier. For example, after copying-and-pasting the entire isolatedNodes rule into Arbori panel, what nodes does it query at the parse tree of the above CREATE TABLE example?