Skip to content

Commit 94829ed

Browse files
authored
Merge pull request #10 from oracle/Offset-mismatches,-reference-errors
Offset mismatches, reference errors
2 parents 4de5087 + 8bde15f commit 94829ed

File tree

8 files changed

+305
-17
lines changed

8 files changed

+305
-17
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ npm run example-cli -- ./test/department_employees.quicksql
8282
The QSQL to DDL translator is the product's core component, It allows users
8383
to transform a QSQL string into an Oracle SQL string.
8484
85-
The Quick SQL Syntax is documented [here](./quicksql.md)
85+
The Quick SQL Syntax is documented [here](./doc/quicksql.md)
8686
8787
See below for examples of how to use this library.
8888

doc/error_diagnostics.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Error Diagnostics
2+
3+
# API
4+
5+
Error diagnostics procedure is called like this
6+
7+
8+
let output = ddl.errorMsgs(`dept
9+
col1
10+
"is this table or misaligned column?"`);
11+
12+
13+
It returns an array of the Error objects
14+
15+
16+
function SyntaxError( message, from, to, severity ) {
17+
this.from = from;
18+
this.to = to;
19+
if( to == null )
20+
this.to = new Offset(from.line, from.depth+1);
21+
this.message = message;
22+
this.severity = severity; // warning or error
23+
if( severity == null )
24+
this.severity = 'error';
25+
}
26+
27+
28+
where error message offsets are stuctured as follows
29+
30+
31+
function Offset( line, depth ) {
32+
this.line = line; // 0-based
33+
this.depth = depth; // 0-based
34+
}
35+
36+
37+
# Error Types
38+
39+
## Misaligned Attributes
40+
41+
Indentation refers to the spaces at the beginning of a code line. Where in most of the other other programming languages the indentation in code is for readability only, QSQL uses indentation to define QSQL tree/forest structure. Parent-child relationship in this tree is interpreted as ether table containing list of columns, or as 2 tables related with referential integrity key constraint.
42+
43+
Inconsistent indentation is detected, with the error message designed to resolve any confusion.
44+
45+
## Invalid Object references
46+
47+
### Invalid Table reference in a View
48+
49+
Exanple:
50+
51+
dept
52+
name
53+
54+
view customer_view customer
55+
56+
57+
Here the `customer` table (or view) is missing.
58+
59+
### Invalid foreign key
60+
61+
Exanple:
62+
63+
department
64+
name
65+
empployee
66+
dept /fk dept
67+
68+
Here the `dept` table (or view) is missing.
69+
70+
### Duplicate ID
71+
72+
Since identity columns are generated by default, specifying them explictly like this
73+
74+
dept
75+
id
76+
77+
would be diagnosed an error.
78+
79+
### Misconstructed column type
80+
81+
QSQL also recognizes syntactically invalid column types like this
82+
83+
dept
84+
name vc-200
85+
name vc0
86+
87+
88+
89+
File renamed without changes.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@oracle/quicksql",
3-
"version": "1.1.1",
3+
"version": "1.1.2",
44
"description": "Quick SQL to DDL translator and GUI",
55
"main": "src/index.js",
66
"type": "module",

src/ddl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ const ddl = (function () {
444444
this.toDDL(input, options);
445445
this.options = JSON.parse(JSON.stringify(this.appliedOptions));
446446

447-
const ret = errorMsgs.findErrors(this);
447+
const ret = errorMsgs.findErrors(this, input);
448448

449449
this.appliedOptions = JSON.parse(JSON.stringify(this.options));
450450
this.resetOptions();

src/errorMsgs.js

Lines changed: 164 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,206 @@
1+
import split_str from './split_str.js';
12

23
const findErrors = (function () {
34

4-
function ErrorMsg( line, offset, message ) {
5-
this.line = line;
6-
this.offset = offset;
5+
function SyntaxError( message, from, to, severity ) {
6+
this.from = from;
7+
this.to = to;
8+
if( to == null )
9+
this.to = new Offset(from.line, from.depth+1);
710
this.message = message;
11+
this.severity = severity; // warning or error
12+
if( severity == null )
13+
this.severity = 'error';
14+
}
15+
16+
function Offset( line, depth ) {
17+
this.line = line; // 0-based
18+
this.depth = depth; // 0-based
819
}
920

10-
function checkSyntax( ddlInstance ) {
21+
function checkSyntax( ddlInstance, input ) {
1122
const ddl = ddlInstance;
1223

1324
let ret = [];
1425

26+
const lines = input.split("\n");
27+
28+
ret = ret.concat(line_mismatch(lines));
1529
const descendants = ddl.descendants();
1630

1731
for( let i = 0; i < descendants.length; i++ ) {
1832
const node = descendants[i];
1933
if( ddl.optionEQvalue('genpk', true) && descendants[i].parseName() == 'id' ) {
2034
const depth = node.content.toLowerCase().indexOf('id');
21-
ret.push(new ErrorMsg( node.line, depth, messages.duplicateId));
35+
ret.push(new SyntaxError( messages.duplicateId, new Offset(node.line, depth) ));
2236
continue;
2337
}
2438
const src2 = node.src[2];
2539
if( 2 < node.src.length && src2.value == '-' ) {
2640
const depth = src2.begin;
27-
ret.push(new ErrorMsg( node.line,depth, messages.invalidDatatype));
41+
ret.push(new SyntaxError( messages.invalidDatatype, new Offset(node.line,depth) ));
2842
continue;
2943
}
3044
const src1 = node.src[1];
3145
if( 1 < node.src.length && 0 < src1.value.indexOf('0') ) {
3246
const depth = src1.begin;
33-
ret.push(new ErrorMsg( node.line,depth, messages.invalidDatatype));
47+
ret.push(new SyntaxError( messages.invalidDatatype, new Offset(node.line,depth) ));
3448
continue;
3549
}
50+
51+
ret = ret.concat(ref_error_in_view(ddl,node));
52+
ret = ret.concat(fk_ref_error(ddl,node));
3653
}
3754

3855
return ret;
3956
}
4057

58+
function ref_error_in_view( ddl, node ) {
59+
var ret = [];
60+
61+
var line = node.content.toLowerCase();
62+
if( node.parseType() == 'view' ) {
63+
var chunks = split_str(line,' '); //line.split(' ');
64+
let pos = 0;
65+
for( var j = 0; j < chunks.length; j++ ) {
66+
pos += chunks[j].length;
67+
if( chunks[j] == ' ' )
68+
continue;
69+
if( chunks[j] == 'view' )
70+
continue;
71+
if( j == 1 )
72+
continue;
73+
var tbl = ddl.find(chunks[j]);
74+
if( tbl == null ) {
75+
ret.push( new SyntaxError(
76+
messages.undefinedObject+chunks[j],
77+
new Offset(node.line, pos-chunks[j].length)
78+
));
79+
}
80+
}
81+
}
82+
return ret;
83+
}
84+
85+
function fk_ref_error( ddl, node ) {
86+
var ret = [];
87+
var line = node.content.toLowerCase();
88+
if( 0 < line.indexOf("/fk") || 0 < line.indexOf("/reference") ) {
89+
let chunks = split_str(line,' '); //line.split(' ');
90+
let pos = 0;
91+
let refIsNext = false;
92+
for( var j = 0; j < chunks.length; j++ ) {
93+
pos += chunks[j].length;
94+
if( chunks[j] == ' ' )
95+
continue;
96+
if( chunks[j] == "/fk" || 0 == chunks[j].indexOf("/reference") ) {
97+
refIsNext = true;
98+
continue;
99+
}
100+
if( !refIsNext )
101+
continue;
102+
var tbl = ddl.find(chunks[j]);
103+
if( tbl == null ) {
104+
ret.push( new SyntaxError(
105+
messages.undefinedObject+chunks[j],
106+
new Offset(node.line, pos-chunks[j].length)
107+
));
108+
break;
109+
}
110+
}
111+
}
112+
return ret;
113+
}
114+
115+
function line_mismatch( lines ) {
116+
var ret = [];
117+
118+
var indent = guessIndent( lines )
119+
120+
for( var i = 1; i < lines.length; i++ ) {
121+
var priorline = lines[i-1];
122+
var line = lines[i];
123+
124+
var priorIndent = depth(priorline);
125+
var lineIndent = depth(line);
126+
127+
if( lineIndent == 0 )
128+
continue;
129+
130+
if( priorIndent < lineIndent && lineIndent < priorIndent+indent )
131+
ret.push(new SyntaxError(
132+
messages.misalignedAttribute+indent,
133+
new Offset(i, lineIndent)
134+
)
135+
);
136+
}
137+
138+
return ret;
139+
}
41140
return checkSyntax;
42141
}());
43142

143+
144+
145+
function guessIndent( lines ) {
146+
let depths = [];
147+
148+
for( var i = 0; i < lines.length; i++ ) {
149+
var line = lines[i];
150+
depths[i] = depth(line);
151+
}
152+
153+
let frequencies = [];
154+
for( let i = 0; i < depths.length; i++ ) {
155+
let j = parentIndex(depths, i);
156+
if( j != null ) {
157+
let f = frequencies[depths[i]-depths[j]];
158+
if( f == null )
159+
f = 0;
160+
frequencies[depths[i]-depths[j]] = f+1;
161+
}
162+
}
163+
164+
let indent = null;
165+
for( let i in frequencies ) {
166+
if( indent == null || frequencies[indent] <= frequencies[i] )
167+
indent = i;
168+
}
169+
return indent;
170+
171+
}
172+
173+
function depth( line ) {
174+
var chunks = line.split(/ |\t/);
175+
var offset = 0;
176+
for( var j = 0; j < chunks.length; j++ ) {
177+
var chunk = chunks[j];
178+
if( "\t" == chunk ) {
179+
offset += 4; //TODO;
180+
}
181+
if( "" == chunk ) {
182+
offset++;
183+
continue;
184+
}
185+
if( !/[^.a-zA-Z0-9_"]/.test(chunk) )
186+
return offset;
187+
}
188+
return 0;
189+
}
190+
191+
function parentIndex( depths, lineNo ) {
192+
for( let i = lineNo; 0 <= i; i-- )
193+
if( depths[i] < depths[lineNo] )
194+
return i;
195+
return null;
196+
}
197+
198+
44199
const messages = {
45200
duplicateId: 'Explicit ID column conflicts with genpk',
46201
invalidDatatype: 'Invalid Datatype',
202+
undefinedObject: 'Undefined Object: ',
203+
misalignedAttribute: 'Misaligned Table or Column; apparent indent = ',
47204
}
48205

49206
export default {findErrors, messages};

test/error_msg_tests.js

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,25 @@ import errorMsgs from '../src/errorMsgs.js'
44

55
function checkError(msgList, line, offset, msg) {
66
for( const i in msgList ) {
7-
if( msgList[i].line == line && msgList[i].offset == offset && msgList[i].message == msg )
7+
if( msgList[i].from.line == line && msgList[i].from.depth == offset && msgList[i].message == msg ) {
88
return;
9+
}
910
}
1011
throw new Error('Test failed for "'+msg+'" @line= '+line);
1112
}
1213

13-
var output;
14-
var input;
14+
export function checkNoError(msgList, msgPrefix) {
15+
for( const i in msgList ) {
16+
if( msgList[i].message.indexOf(msgPrefix) == 0 ) {
17+
throw new Error('Test failed for "'+msg);
18+
}
19+
}
20+
}
21+
1522

1623
export default function error_msg_tests() {
1724

18-
output = ddl.errorMsgs(`dept
25+
let output = ddl.errorMsgs(`dept
1926
id
2027
`);
2128
checkError(output, 1, 4, errorMsgs.messages.duplicateId);
@@ -26,6 +33,35 @@ export default function error_msg_tests() {
2633
`);
2734
checkError(output, 1, 4+4+2+1, errorMsgs.messages.invalidDatatype);
2835
checkError(output, 2, 4+4+1, errorMsgs.messages.invalidDatatype);
36+
checkNoError(output, errorMsgs.messages.misalignedAttribute);
37+
38+
output = ddl.errorMsgs(`dept
39+
name
40+
customer
41+
dept /fk department
42+
`);
43+
checkError(output, 3, 4+4+1+3+1, errorMsgs.messages.undefinedObject+'department');
44+
checkNoError(output, errorMsgs.messages.misalignedAttribute);
45+
46+
output = ddl.errorMsgs(`dept
47+
name
48+
view customer_view customer
49+
`);
50+
checkError(output, 2, 4+1+13+1, errorMsgs.messages.undefinedObject+'customer');
51+
checkNoError(output, errorMsgs.messages.misalignedAttribute);
52+
53+
output = ddl.errorMsgs(`dept
54+
col1
55+
"is this table or misaligned column?"
56+
`);
57+
checkError(output, 2, 4, errorMsgs.messages.misalignedAttribute+"3");
58+
59+
output = ddl.errorMsgs(`dept
60+
col1
61+
col2
62+
"is this table or misaligned column?"
63+
`);
64+
checkError(output, 3, 4, errorMsgs.messages.misalignedAttribute+"3");
2965

3066
}
3167

0 commit comments

Comments
 (0)