Skip to content

Instantly share code, notes, and snippets.

@yoichiro
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yoichiro/a192cf77abac52cf519a to your computer and use it in GitHub Desktop.
Save yoichiro/a192cf77abac52cf519a to your computer and use it in GitHub Desktop.
QueryDivider - A parser to divide SQL queries with a delimiter string which is decided by the "delimiter" command.
/*
* var parser = new QueryDivider();
* var result = parser.parse(sql);
* if (result.success) {
* var result = result.result;
* for (var i = 0; i < result.length; i++) {
* var query = result[i];
* // do something...
* }
* }
*
*/
var ParseError = function(message) {
this.message = message;
};
ParseError.prototype = new Error();
ParseError.prototype.name = "ParseError";
var QueryDivider = function() {
this.DELIMITER = "delimiter ";
this.initialize();
};
QueryDivider.prototype = {
initialize: function() {
this.result = [];
this.stateMap = {
query: this.query.bind(this),
lineStart: this.lineStart.bind(this),
escapedQuery: this.escapedQuery.bind(this),
sharpComment: this.sharpComment.bind(this),
maybeDashComment: this.maybeDashComment.bind(this),
dashComment: this.dashComment.bind(this),
maybeInlineCommentStart: this.maybeInlineCommentStart.bind(this),
inlineComment: this.inlineComment.bind(this),
maybeInlineCommentEnd: this.maybeInlineCommentEnd.bind(this),
maybeDelimiterDef: this.maybeDelimiterDef.bind(this),
delimiterDef: this.delimiterDef.bind(this),
delimiterDefEnd: this.delimiterDefEnd.bind(this),
maybeDelimiter: this.maybeDelimiter.bind(this)
};
this.currentState = this.stateMap.lineStart;
this.buffer = [];
this.maybeDashCommentCount = 0;
this.maybeDelimiterDefBuffer = [];
this.maybeDelimiterDefCount = 0;
this.delimiterDefCandidate = [];
this.delimiter = ";";
this.maybeDelimiterCount = 0;
this.skipDelimiterCheck = false;
},
parse: function(query) {
try {
this.apply(query, 0);
this.appendBufferToResult();
return {
success: true,
result: this.result
};
} catch(e) {
if (e instanceof ParseError) {
return {
success: false,
error: e
};
} else {
throw e;
}
}
},
apply: function(query, pos) {
if (query.length === pos) {
return;
}
var ch = query.charAt(pos);
var incr = this.currentState(query, ch, pos);
this.apply(query, pos + incr);
},
lineStart: function(query, ch, pos) {
//console.log("lineStart: " + ch);
if (ch === ' ') {
this.buffer.push(ch);
return 1;
} else if (ch === 'd') {
this.currentState = this.stateMap.maybeDelimiterDef;
this.maybeDelimiterDefBuffer = [ch];
this.maybeDelimiterDefCount = 0;
return 1;
} else {
this.buffer.push(ch);
this.currentState = this.stateMap.query;
return 1;
}
},
query: function(query, ch, pos) {
//console.log("query: " + ch + " [delimiter=" + this.delimiter + "]");
var skipDelimiterCheck = this.skipDelimiterCheck;
this.skipDelimiterCheck = false;
if (ch === '\\') {
this.buffer.push(ch);
this.currentState = this.stateMap.escapedQuery;
return 1;
} else if (!skipDelimiterCheck && ch === this.delimiter.charAt(0) && this.delimiter.length === 1) {
this.appendBufferToResult();
return 1;
} else if (!skipDelimiterCheck && ch === this.delimiter.charAt(0)) {
this.currentState = this.stateMap.maybeDelimiter;
this.maybeDelimiterCount = 0;
return 1;
} else if (ch === '#') {
this.buffer.push(ch);
this.currentState = this.stateMap.sharpComment;
return 1;
} else if (ch === '-') {
this.buffer.push(ch);
this.currentState = this.stateMap.maybeDashComment;
this.maybeDashCommentCount = 0;
return 1;
} else if (ch === '/') {
this.buffer.push(ch);
this.currentState = this.stateMap.maybeInlineCommentStart;
return 1;
} else if (ch === '\n') {
this.buffer.push(ch);
this.currentState = this.stateMap.lineStart;
return 1;
} else {
this.buffer.push(ch);
return 1;
}
},
escapedQuery: function(query, ch, pos) {
//console.log("escapedQuery: " + ch);
this.buffer.push(ch);
this.currentState = this.stateMap.query;
return 1;
},
sharpComment: function(query, ch, pos) {
//console.log("sharpComment: " + ch);
if (ch === '\n') {
this.buffer.push(ch);
this.currentState = this.stateMap.lineStart;
return 1;
} else {
this.buffer.push(ch);
return 1;
}
},
maybeDashComment: function(query, ch, pos) {
//console.log("maybeDashComment: " + ch);
if (this.maybeDashCommentCount === 0 && ch === '-') {
this.buffer.push(ch);
this.maybeDashCommentCount++;
return 1;
} else if (this.maybeDashCommentCount === 1 && ch === ' ') {
this.buffer.push(ch);
this.currentState = this.stateMap.dashComment;
return 1;
} else {
this.buffer.push(ch);
this.currentState = this.stateMap.query;
return 1;
}
},
dashComment: function(query, ch, pos) {
//console.log("dashComment: " + ch);
if (ch === '\n') {
this.buffer.push(ch);
this.currentState = this.stateMap.lineStart;
return 1;
} else {
this.buffer.push(ch);
return 1;
}
},
maybeInlineCommentStart: function(query, ch, pos) {
//console.log("maybeInlineCommentStart: " + ch);
if (ch === '*') {
this.buffer.push(ch);
this.currentState = this.stateMap.inlineComment;
return 1;
} else {
this.buffer.push(ch);
this.currentState = this.stateMap.query;
return 1;
}
},
inlineComment: function(query, ch, pos) {
//console.log("inlineComment: " + ch);
if (ch === '*') {
this.buffer.push(ch);
this.currentState = this.stateMap.maybeInlineCommentEnd;
return 1;
} else {
this.buffer.push(ch);
return 1;
}
},
maybeInlineCommentEnd: function(query, ch, pos) {
//console.log("maybeInlineCommentEnd: " + ch);
if (ch === '*') {
this.buffer.push(ch);
return 1;
} else if (ch === '/') {
this.buffer.push(ch);
this.currentState = this.stateMap.query;
return 1;
} else {
this.buffer.push(ch);
this.currentState = this.stateMap.inlineComment;
return 1;
}
},
maybeDelimiterDef: function(query, ch, pos) {
//console.log("maybeDelimiterDef: " + ch);
this.maybeDelimiterDefCount++;
if (ch === this.DELIMITER.charAt(this.maybeDelimiterDefCount)) {
if ((this.maybeDelimiterDefCount + 1) === this.DELIMITER.length) {
this.currentState = this.stateMap.delimiterDef;
this.delimiterDefCandidate = [];
return 1;
} else {
this.maybeDelimiterDefBuffer.push(ch);
return 1;
}
} else {
this.buffer = this.buffer.concat(this.maybeDelimiterDefBuffer);
this.buffer.push(ch);
this.currentState = this.stateMap.query;
return 1;
}
},
delimiterDef: function(query, ch, pos) {
//console.log("delimiterDef: " + ch);
if (ch === ' ') {
if (this.delimiterDefCandidate.length === 0) {
return 1;
} else {
this.delimiter = this.delimiterDefCandidate.join("");
this.currentState = this.stateMap.delimiterDefEnd;
this.appendBufferToResult();
return 1;
}
} else if (ch === '\n') {
if (this.delimiterDefCandidate.length === 0) {
throw new ParseError("Delimiter not defined at " + pos);
} else {
this.delimiter = this.delimiterDefCandidate.join("");
this.currentState = this.stateMap.lineStart;
this.appendBufferToResult();
return 1;
}
} else {
this.delimiterDefCandidate.push(ch);
return 1;
}
},
delimiterDefEnd: function(query, ch, pos) {
//console.log("delimiterEnd: " + ch);
if (ch === '\n') {
this.currentState = this.stateMap.lineStart;
return 1;
} else {
return 1;
}
},
maybeDelimiter: function(query, ch, pos) {
//console.log("maybeDelimiter: " + ch);
this.maybeDelimiterCount++;
if (ch === this.delimiter.charAt(this.maybeDelimiterCount)) {
if ((this.maybeDelimiterCount + 1) === this.delimiter.length) {
this.appendBufferToResult();
this.currentState = this.stateMap.query;
return 1;
} else {
return 1;
}
} else {
this.currentState = this.stateMap.query;
this.skipDelimiterCheck = true;
return this.maybeDelimiterCount * -1;
}
},
appendBufferToResult: function() {
var joined = this.buffer.join("");
var candidate = this.trim(joined);
if (candidate) {
this.result.push(joined);
}
this.buffer = [];
},
trim: function(str) {
return str.replace(/^[  \t\r\n]+|[  \t\r\n]+$/g, "");
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment