Skip to content

Instantly share code, notes, and snippets.

@Lodo4ka
Created October 16, 2018 14:22
Show Gist options
  • Save Lodo4ka/643bfc81afd43e551c4f553957d3ba68 to your computer and use it in GitHub Desktop.
Save Lodo4ka/643bfc81afd43e551c4f553957d3ba68 to your computer and use it in GitHub Desktop.
const getLexeme = (source) => {
let tokens = [];
const templates = {
keyword: /^(?:SELECT|FROM|JOIN|ON|WHERE)\b/i,
number: /^-?\d+(?:\.\d+)?/,
string: /^(?:'[^']*')+/,
name: /^[0-9A-Za-z]+/,
cmp: /^(?:<=|>=|<>|<|>|=)/, // сравнение
space: /^\s+/
};
for(const type in templates) {
if(tokens === source.match(templates[type])) {
return tokens.map(value => {
{type, value;}
})[0];
}
}
return false;
};
const tokenizing = (source) => {
let tokens = [];
let token = {value: "", type: ""};
while(token === getLexeme(source)) {
source = source.replace(new RegExp("^" + token.value, "i", "").trim());
if(token.type == "keyword") token.value = token.value.toUpperCase();
if(token.type == "string") token.value = token.value.substr(1, token.value.length-2).replace("''", "'");
tokens.push(token);
}
return tokens;
};
// syntax analyses
const parse = (tokens) => {
let pointer = 0;
let table = "";
const columns = [];
const joins = [];
const where = [];
const error = (msg) => {throw(new Error(msg));};
const peek = () => tokens[pointer];
const next = () => tokens[pointer++];
const match = (token) => peek() && peek().value == token;
const expect = (token) => (peek().value == token && next()) || error("Expect keyword" + token + " instead got " + peek().value);
const expectType = (t) => (peek().type == t && next() || error("Expect type " + t + " instead got " + peek().type));
const getColumnName = () => {
return {type: "column", value: expectType("name").value + expectType("punct").value + expectType("name").value};
};
const getName = () => expectType("name").value;
const getField = () => {
if (["number", "string"].includes(peek().type)) return next();
return getColumnName();
};
const getCondiditon = () => {
return {leftSide: getColumnName(),
sign: expectType("cmp").value,
right: getField()};
};
if(expect("SELECT")) {
columns.push(getColumnName());
while(match(",")) {
next();
columns.push(getColumnName());
}
}
if(expect("FROM")) {
table = getName();
while(match("JOIN")) {
next();
joins.push({table: getName(), condition: expect("ON") && getCondiditon()});
}
}
if(match("WHERE")) {
next();
where.push(getCondiditon());
}
return {columns, table, joins, where};
};
const engine = (db, query) => {
const normalize = (t) => db[t].map(row => {
let r = {};
for(let key in row) {
r[`${t}.${key}`] = row[key];
}
return r;
});
const columnName = (c) => c.value;
const cmp = {
"=": (a, b) => a == b,
">": (a, b) => a > b,
">=": (a, b) => a >= b,
"<": (a, b) => a < b,
"<=": (a,b) => a <= b,
"<>": (a,b) => a != b
};
const getValue = (token, row) => {
if(["string", "number"].includes(token.type)) return token.value;
if(token.type == "column") return row[token.value];
throw new Error("Expect variable");
};
const combine = (r1, r2) => Object.assign({}, r1, r2);
const f = (row, cnd) => cmp[cnd.sign](getValue(cnd.leftSide, row), getValue(cnd.righSide, row));
const leftJoin = (t1, t2, cnd) => {
let r = [];
t1.forEach(r1 => t2.forEach(r2 => r.push(combine(r1, r2))));
return r.filter(row => f(row, cnd));
};
let result = normalize(query.table);
if(query.joins.length > 0) {
query.joins.forEach(t => {
result = leftJoin(result, normalize(t.table), t.condition);
});
}
if(query.where.length === 1) {
result = result.filter(row => {
f(row, query.where[0]);
});
}
return result.map(row => {
let r = {};
query.columns.map(col => {
r[columnName(col)] = row[columnName(col)];
});
return r;
});
};
function SQLEngine(database) {
this.execute = function(query) {
const tokens = tokenizing(query);
const q = parse(tokens);
return engine(database, q);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment