Skip to content

Commit

Permalink
Merge pull request #22 from oracle/issue-21
Browse files Browse the repository at this point in the history
Issue 21
  • Loading branch information
mig8447 authored Nov 7, 2023
2 parents d7639ba + aceb598 commit 9c01e0c
Show file tree
Hide file tree
Showing 13 changed files with 1,087 additions and 1,036 deletions.
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${file}"
}
]
}
2 changes: 1 addition & 1 deletion dist/quick-erd.js
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ class de {
e.rearrangeDiagram(3, !1), this.paperScroller.centerContent();
}
}
const ue = "1.1.4", fe = {
const ue = "1.1.5", fe = {
Diagram: de,
version: ue
};
Expand Down
2 changes: 1 addition & 1 deletion dist/quick-erd.umd.cjs

Large diffs are not rendered by default.

1,600 changes: 806 additions & 794 deletions dist/quick-sql.js

Large diffs are not rendered by default.

238 changes: 119 additions & 119 deletions dist/quick-sql.umd.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oracle/quicksql",
"version": "1.1.4",
"version": "1.1.5",
"description": "Quick SQL to DDL and ERD translator",
"main": "./dist/quick-sql.umd.cjs",
"module": "./dist/quick-sql.js",
Expand Down
79 changes: 5 additions & 74 deletions src/ddl.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import tree from './tree.js';
import lexer from './lexer.js';
import json2qsql from './json2qsql.js'
import errorMsgs from './errorMsgs.js'
import {canonicalObjectName} from './naming.js'

const ddl = (function () {
function DDL() {
Expand Down Expand Up @@ -51,6 +52,9 @@ const ddl = (function () {
this.options = JSON.parse(JSON.stringify(this.defaultOptions));
this.forest = null;

this


this.normalize = function( iNput ) {
if( iNput == null )
return null;
Expand Down Expand Up @@ -143,86 +147,13 @@ const ddl = (function () {
var descendants = this.forest[i].descendants();
for( var j = 0; j < descendants.length; j++ ) {
var node = descendants[j];
if( node.parseName() == this.canonicalObjectName(name) )
if( node.parseName() == canonicalObjectName(name) )
return node;
}
}
return null;
};

this.canonicalObjectName = function( nAme ) {
if( nAme == null )
return null;
if( nAme.indexOf('"') == 0 )
return nAme;
let possiblyQuoted = this.quoteIdentifier(nAme);
if( possiblyQuoted.indexOf('"') == 0 )
return possiblyQuoted;
possiblyQuoted = possiblyQuoted.replace(/ /g,"_");
return possiblyQuoted;
};
/**
* Ported from dbtools-common Service.quoteIdentifier() and AMENDED for QSQL usage
* Diffs: 1. Space separator does not warrant double quote
* 2. Lower case letters do not warrant double quote
*
* Quotes an identifier based on the following:
* 1) Any lower/mixed case identifier is quoted
* 2) Any identifier starting with the quote string is NOT quoted. If it is currently quoted (first/last characters
* are the quote character) then no quoting is needed, and if it not quoted but contains a quote character, the name is
* invalid.
* 3) The string is checked for invalid characters; any invalid characters require the string be quoted. A valid identifier
* starts with a letter and contains only letters, digits, or one of _$#. Note that the database allows any valid character
* in the database character set. We by policy use the basic ASCII character set to determine letters.
*
* Conflicting requirements:
* - SYS.DBMS_SQLTUNE.ACCEPT_ALL_SQL_PROFILES composite object names shouldn't be quoted
* - Bug 20667620: weird column name "NO." should
* @param s
* @param quoteChar
* @return
*/
this.quoteIdentifier = function(/*String*/ s, /*char*/ quoteChar ) {
let quoteString = '"'; //String.valueOf(quoteChar);
if( s == null ) // to be able to safely wrap any string value
return null;

// We want to check 3 things here:
// 1) The string is a valid identifier (starts with a letter, contains only valid characters,--!!!-- don't care if a reserved word
// 2) The string is all upper case. By policy, we will quote any lower or mixed cased strings
// 3) the string is not currently quoted. We will only check the first character for a quote. A quote appearing anywhere
// else or a starting quote w/o an ending quote is going to make the string invalid no matter what (quotes cannot
// be escaped in Oracle identifiers).
let quote = false;
// Bug 9215024 column name can have "_" so we can't exclude them.
// ^^^^^^^^^^
// Edit Wars: the loop below iterates through list of characters, and as soon
// it encounters illegitimate symbol, it sets the "need quotation" flag
const legitimateChars = "$#_ "; // !NO ".": see this function JavaDoc above
if ( !s.startsWith(quoteString) && !quote ) {
const chars = s; //new char[s.length()];
//s.getChars(0, chars.length, chars, 0);
if( chars.length > 0 && '0' <= chars[0] && chars[0] <= '9' )
quote = true;
else for ( let i in chars ) {
const c = chars[i];
if( legitimateChars.indexOf(c) < 0 && (
c < '0' || '9' < c && c < 'A' || 'Z' < c && c < 'a' || 'z' < c
)) {
quote = true;
break;
}
// wierd case with double quote inside
// ...
}
}
if( s.startsWith("_") || s.startsWith("$") || s.startsWith("#") )
quote = true;
if( !quote )
quoteString = '';
return quoteString+s+quoteString;
}


/**
* @param {*} input
Expand Down
2 changes: 1 addition & 1 deletion src/json2qsql.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import singular from './singular.js';
import {singular} from './naming.js';
import ddl from "../src/ddl.js";


Expand Down
116 changes: 116 additions & 0 deletions src/naming.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
export function singular( name ) {
// ddl.options["Duality View"].value != 'yes'
// identity columns in JSON document are singular
if( name == null )
return name;
if( name.toUpperCase().endsWith('IES') )
return name.substring(0,name.length-3)+'y';
if( name.toUpperCase().endsWith('ES') )
return name.substring(0,name.length-1);
if( name.toUpperCase().endsWith('S') )
return name.substring(0,name.length-1);
return name;
}

/**
* Ported from dbtools-common Service.quoteIdentifier() and AMENDED for QSQL usage
* Diffs: 1. Space separator does not warrant double quote
* 2. Lower case letters do not warrant double quote
*
* Quotes an identifier based on the following:
* 1) Any lower/mixed case identifier is quoted
* 2) Any identifier starting with the quote string is NOT quoted. If it is currently quoted (first/last characters
* are the quote character) then no quoting is needed, and if it not quoted but contains a quote character, the name is
* invalid.
* 3) The string is checked for invalid characters; any invalid characters require the string be quoted. A valid identifier
* starts with a letter and contains only letters, digits, or one of _$#. Note that the database allows any valid character
* in the database character set. We by policy use the basic ASCII character set to determine letters.
*
* Conflicting requirements:
* - SYS.DBMS_SQLTUNE.ACCEPT_ALL_SQL_PROFILES composite object names shouldn't be quoted
* - Bug 20667620: weird column name "NO." should
* @param s
* @param quoteChar
* @return
*/
function quoteIdentifier(/*String*/ s, /*char*/ quoteChar ) {
let quoteString = '"'; //String.valueOf(quoteChar);
if( s == null ) // to be able to safely wrap any string value
return null;

// We want to check 3 things here:
// 1) The string is a valid identifier (starts with a letter, contains only valid characters,--!!!-- don't care if a reserved word
// 2) The string is all upper case. By policy, we will quote any lower or mixed cased strings
// 3) the string is not currently quoted. We will only check the first character for a quote. A quote appearing anywhere
// else or a starting quote w/o an ending quote is going to make the string invalid no matter what (quotes cannot
// be escaped in Oracle identifiers).
let quote = false;
// Bug 9215024 column name can have "_" so we can't exclude them.
// ^^^^^^^^^^
// Edit Wars: the loop below iterates through list of characters, and as soon
// it encounters illegitimate symbol, it sets the "need quotation" flag
const legitimateChars = "$#_ "; // !NO ".": see this function JavaDoc above
if ( !s.startsWith(quoteString) && !quote ) {
const chars = s; //new char[s.length()];
//s.getChars(0, chars.length, chars, 0);
if( chars.length > 0 && '0' <= chars[0] && chars[0] <= '9' )
quote = true;
else for ( let i in chars ) {
const c = chars[i];
if( legitimateChars.indexOf(c) < 0 && (
c < '0' || '9' < c && c < 'A' || 'Z' < c && c < 'a' || 'z' < c
)) {
quote = true;
break;
}
// wierd case with double quote inside
// ...
}
}
if( s.startsWith("_") || s.startsWith("$") || s.startsWith("#") )
quote = true;
if( !quote )
quoteString = '';
return quoteString+s+quoteString;
}

export function canonicalObjectName( nAme ) {
if( nAme == null )
return null;
if( nAme.indexOf('"') == 0 )
return nAme;
let possiblyQuoted = quoteIdentifier(nAme);
if( possiblyQuoted.indexOf('"') == 0 )
return possiblyQuoted;
possiblyQuoted = possiblyQuoted.replace(/ /g,"_");
return possiblyQuoted;
};


export function concatNames(chunk1, chunk2, chunk3) {
let quote = false;
if( chunk3 == null )
chunk3 = '';
if( 0 == chunk1.indexOf('"') ) {
quote = true;
chunk1 = chunk1.substring(1,chunk1.length-1);
}
if( 0 == chunk2.indexOf('"') ) {
quote = true;
chunk2 = chunk2.substring(1,chunk2.length-1);
}
if( 0 == chunk3.indexOf('"') ) {
quote = true;
chunk3 = chunk3.substring(1,chunk3.length-1);
}
let ret = chunk1+chunk2+chunk3;
if( quote )
ret = '"'+ret+'"';
else
ret = ret.toLowerCase();

return ret;
}


export default {singular, canonicalObjectName, concatNames};
15 changes: 0 additions & 15 deletions src/singular.js

This file was deleted.

25 changes: 14 additions & 11 deletions src/tree.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import singular from './singular.js';
import {singular,concatNames,canonicalObjectName} from './naming.js';
import translate from './translate.js';
import sample from './sample.js';
import lexer from './lexer.js';
Expand Down Expand Up @@ -206,7 +206,7 @@ let tree = (function(){
if (c >= '0' && c <= '9') {
rEt = 'x'+rEt;
}
return amend_reserved_word(ddl.canonicalObjectName(rEt));
return amend_reserved_word(canonicalObjectName(rEt));
};
this.parseType = function( pure ) {
if( this.children != null && 0 < this.children.length )
Expand Down Expand Up @@ -259,20 +259,21 @@ let tree = (function(){
if( src[0].value.endsWith('id') && vcPos < 0 && this.indexOf('/')+1 == this.indexOf('pk') )
ret = 'number';

let parent_child = concatNames(parent.parseName(),'_',this.parseName());

if( src[0].value.endsWith('_yn') || src[0].value.startsWith('is_') ) {
ret = 'varchar2(1 char) constraint '+ddl.objPrefix()+parent.parseName()+'_'+this.parseName()+'\n';
ret = 'varchar2(1 char) constraint '+concatNames(ddl.objPrefix(),parent_child)+'\n';
ret += tab + tab+' '.repeat(parent.maxChildNameLen()) +'check ('+this.parseName()+" in ('Y','N'))";
}
for( let i in boolTypes ) {
let pos = this.indexOf(boolTypes[i]);
if( 0 < pos ) {
ret = 'varchar2(1 char) constraint '+ddl.objPrefix()+parent.parseName()+'_'+this.parseName()+'\n';
ret = 'varchar2(1 char) constraint '+concatNames(ddl.objPrefix(),parent_child)+'\n';
ret += tab + tab+' '.repeat(parent.maxChildNameLen()) +'check ('+this.parseName()+" in ('Y','N'))";
break;
}
}


if( this.indexOf('phone_number') == 0 )
ret = 'number';
let from = this.indexOf('num', true);
Expand Down Expand Up @@ -315,9 +316,10 @@ let tree = (function(){
return ret;
}


if( 0 < this.indexOf('unique') ) {
ret += '\n';
ret += tab + tab+' '.repeat(parent.maxChildNameLen()) +'constraint '+parent.parseName()+'_'+this.parseName()+'_unq unique';
ret += tab + tab+' '.repeat(parent.maxChildNameLen()) +'constraint '+parent_child+'_unq unique';
}
var optQuote = '\'';
if( ret.startsWith('integer') || ret.startsWith('number') || ret.startsWith('date') )
Expand All @@ -344,15 +346,15 @@ let tree = (function(){
values = values.replace(/,/g,optQuote+','+optQuote);
else
values = values.replace(/ /g,optQuote+','+optQuote);
ret +=' constraint '+ddl.objPrefix()+parent.parseName()+'_'+this.parseName()+'_ck\n';
ret +=' constraint '+concatNames(ddl.objPrefix(),parent_child,'_ck')+'\n';
ret += tab + tab+' '.repeat(parent.maxChildNameLen()) +'check ('+this.parseName()+' in ('+optQuote+values+optQuote+'))';
ret = ret.replace(/''/gm,"'");

}
if( 0 < this.indexOf('between') ) {
const bi = this.indexOf('between');
const values = src[bi+1].value + ' and ' + src[bi+3].value;
ret +=' constraint '+parent.parseName()+'_'+this.parseName()+'_bet\n';
ret +=' constraint '+concatNames(parent_child,'_bet')+'\n';
ret +=' check ('+this.parseName()+' between '+values+')';
}
if( 0 < this.indexOf('pk') ) {
Expand All @@ -366,7 +368,7 @@ let tree = (function(){
if( ret.startsWith('number') && ddl.optionEQvalue('pk', ddl.guid) )
typeModifier = ' default on null to_number(sys_guid(), \'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\') ';
ret += typeModifier +'\n';
ret += tab + tab + ' '.repeat(parent.maxChildNameLen()) +'constraint '+ddl.objPrefix()+parent.parseName()+'_'+this.parseName()+'_pk primary key';
ret += tab + tab + ' '.repeat(parent.maxChildNameLen()) + 'constraint ' + concatNames(ddl.objPrefix(),parent_child,'_pk')+' primary key';
}
return ret;
};
Expand Down Expand Up @@ -602,8 +604,9 @@ let tree = (function(){
typeModifier = 'default on null '+objName+'_seq.NEXTVAL '.toLowerCase();
if( ddl.optionEQvalue('pk', ddl.guid) )
typeModifier = 'default on null to_number(sys_guid(), \'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\') ';
ret += tab + idColName + pad + 'number ' + typeModifier + '\n';
ret += tab + tab+' '.repeat(this.maxChildNameLen()) +'constraint '+objName+'_'+idColName+'_pk primary key,\n';
ret += tab + idColName + pad + 'number ' + typeModifier + '\n';
const obj_col = concatNames(objName,'_',idColName);
ret += tab + tab+' '.repeat(this.maxChildNameLen()) +'constraint '+concatNames(obj_col,'_pk')+' primary key,\n';
} else {
let pkNode = this.getExplicitPkNode();
if( pkNode != null ) {
Expand Down
1 change: 1 addition & 0 deletions test/regression_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ function processFile( subdir, file ) {
output = quicksql.toDDL(text);
const errors = quicksql.errorMsgs(text);
checkNoError(errors, errorMsgs.messages.misalignedAttribute);
checkNoError(errors, errorMsgs.messages.undefinedObject);
}

let cmp = null;
Expand Down
Loading

0 comments on commit 9c01e0c

Please sign in to comment.