Files
Unify-db/source/database.js

1607 lines
25 KiB
JavaScript
Raw Normal View History

2025-12-25 09:56:43 +01:00
/*
Copyright (c) 2020, 2023, The Unified Company.
*/
import tools from './tools.js';
import querySQL from './querySQL.js';
import document from './document.js';
import DATATYPE from './datatype.js';
import path from 'path';
import sqlite from './sqlite.js';
import sqlite3 from 'better-sqlite3';
class databaseManager{
database;
type = "update";
sqlite = new sqlite();
collisionID = 0;
serverID = 0;
async connect() {
if( !document.doctype ) {
this.sqlite.setup();
this.database = this.sqlite.db;
}
}
createTimeUUID() {
const timestamp = Date.now();
++this.collisionID;
var padddedCollisionID = String( this.collisionID % 9 ).padStart( 2, '0' )
var TimeUUID = parseFloat( timestamp + "" + this.serverID + "" + padddedCollisionID );
return TimeUUID;
}
query( query, execute = true ) {
if( query.type == "insert" ) {
query.setValue( "id", this.createTimeUUID() );
}
if( execute && !query.root ) {
query.root = true;
}
if( !query.name ) {
query.name = query.table;
}
var queryString = this.parseQuery( query );
//console.log("query", query);
if( execute ) {
return this.executeQuery( queryString, query );
}
return queryString;
}
parseFilter( query ) {
var searchable = query.filter.search;
if( searchable ) {
//console.log("query.filter", query, searchable);
return this.parseSearch( searchable, query, 0 );
} else {
return false;
}
}
tabLevel( level ) {
var tabs = "";
for (var i = 0; i < level; i++) {
tabs += "\t";
}
return tabs;
}
parseSearchOperator( a, b, searchable, query, level ) {
//console.log("parseSearchOperator", searchable);
var tabs = this.tabLevel( level ) + " ";
var subEnd = "";
var begin = "\n" + tabs;
var end = tabs + "\n";
switch( searchable.operator.replaceAll(/\s/g,'')) {
case "LIKE" :
b = "'%" + tools.htmlEntities( b ) + "%'";
break;
case "=" :
b = "'" + tools.htmlEntities( b ) + "'";
break;
case "OR":
searchable.operator = " " + tabs + searchable.operator + "";
subEnd = tabs;
break;
case "AND":
searchable.operator = "\n " + tabs + searchable.operator + "\n";
subEnd = tabs;
break;
default:
}
return begin + "( " + a + " " + searchable.operator + " " + b + subEnd + " ) " + end;
}
parsePath( searchable, filterAddress, query ) {
var path = searchable.path;
var parts = path.split("/");
var columnName = parts.pop();
var filterAddress = parts.join("/") + "/";
this.findMatchingQuery( filterAddress, query, searchable, path );
}
validateSearchables( a ) {
if( typeof a == "string" ) {
return true;
}
if( a.operator ) {
return true;
}
if( !a.queryName ) {
return false;
}
return true;
}
parseSearch( searchable, query, level ) {
if( searchable.operator ) {
level++;
var a = this.parseSearch( searchable.a, query, level );
var b = this.parseSearch( searchable.b, query, level );
/*
var invalidA = this.validateSearchables( searchable.a )
var invalidB = this.validateSearchables( searchable.b )
if( !invalidA ) {
return false;
}
if( !invalidB ) {
return false;
}
if( !a && typeof a != "string" ) {
return b;
}
if( !b && typeof b != "string" ) {
return a;
}
*/
return this.parseSearchOperator( a, b, searchable, query, level );
}
if( searchable.path ) {
var filterAddress = "";
this.parsePath( searchable, filterAddress, query );
if( !searchable.queryName ) {
//return false;
}
}
if( typeof searchable == "string" ) {
return searchable;
}
if( searchable.queryName ) {
return searchable.queryName;
}
}
findMatchingQueryJoins( query, filterAddress, searchable ) {
for( var c = 0; c< query.joins.length; c++ ) {
var join = query.joins[c];
console.log("search join", join);
//this.findMatchingQuery( filterAddress, join, searchable );
}
}
findMatchingQueryUnions( query, filterAddress, searchable ) {
for (var i = 0; i < query.union.length; i++) {
var union = query.union[i];
this.findMatchingQuery( filterAddress, union, searchable );
}
}
findMatchingQuery( filterAddress, query, searchable, path ) {
//console.log( "query.filterAddress", query.filterAddress );
//console.log( "searchable.path", filterAddress );
var parts = path.split("/");
//console.log("parts", parts);
if( parts.length == 3 ) {
parts.shift();
}
searchable.queryName = parts.join(".");
/*
if( query.filterAddress == filterAddress ) {
var parts = path.split("/");
console.log("parts", parts);
searchable.queryName = path.replace("./", query.name + ".");
} else {
searchable.queryName = path;
console.log("filterAddress", filterAddress);
console.log("filterAddress", searchable);
var parts = filterAddress.split("/")
//console.log("parts", parts);
this.findMatchingQueryJoins( query, filterAddress, searchable );
//this.findMatchingQueryUnions( query, filterAddress, searchable );
}
*/
}
insertRow( table ) {
var query = new querySQL( );
query.type = "insert";
query.addObject( table );
var collection = table.getCollection();
if( collection ) {
query.setColumn( "joined", true ); // todo : fix : set boolean on setColumn
} else {
query.setColumn( "joined", false );
}
var row = this.query( query );
return row.lastInsertRowid;
}
updateChildren( query ) {
var objects = query.objects;
for( var c = 0; c < objects.length; c++ ) {
var object = objects[c];
var values = new Array();
var children = object.getChildren();
for( var c = 0; c < children.length; c++ ) {
var child = children[c];
if( child.datatype ) {
values.push( child.value );
}
}
}
}
exec( queryString ) {
return this.database.prepare( queryString ).all();
}
prepare( queryString ) {
return this.database.prepare( queryString ).all();
}
performUpdateQuery( queryString, query ) {
if( query.objects.length < 2 ) {
var update = this.database.prepare( queryString );
update.run( query.getValues() );
}
if( query.debug ) {
console.log();
console.log( queryString );
console.log();
console.log( query.getValues() );
console.log();
}
if( query.objects.length > 1 ) {
var id = this.updateChildren( query );
} else {
if( global.syncClient && query.sync ) {
var id = query.getWhereByColumnName("id");
global.syncClient.sendMessage( queryString, query, id );
}
}
//if( global.syncClient && query.sync ) {
// global.syncClient.sendMessage( queryString, query, output.lastInsertRowid );
//}
// query.queryUpdateChildren();
}
performInsertQuery( queryString, query ) {
var insert = this.database.prepare( queryString );
if( query.debug ) {
console.log( queryString );
}
var output = insert.run( query.getValues() );
if( global.syncClient && query.sync) {
var id = query.getWhereByColumnName("id");
global.syncClient.sendMessage( queryString, query, output.lastInsertRowid );
}
return output;
}
performDeleteQuery( queryString, query ) {
if( query.debug ) {
console.log(queryString );
}
var output = this.database.prepare( queryString ).run();
if( global.syncClient && query.sync ) {
global.syncClient.sendMessage( queryString, query, output.lastInsertRowid );
}
return output;
}
executeQuery( queryString, query ) {
console.log( queryString );
switch( query.type.toLowerCase() ) {
case "update":
return this.performUpdateQuery( queryString, query );
break;
case "insert":
return this.performInsertQuery( queryString, query );
break;
case "select":
return this.database.prepare( queryString ).all();
break;
case "count":
return this.database.prepare( queryString ).pluck().get();
break;
case "delete":
return this.performDeleteQuery( queryString, query );
break;
}
}
runQuery( queryString ) {
return this.database.prepare( queryString ).all();
}
parseQueryItems( query, queryID ) {
var items = Object.entries( query );
var queryString = "";
for( var c = 0; c < items.length; c++ ){
var currentQuery = items[c];
var name = tools.getClassNameByEntry( currentQuery );
var value = tools.getObjectByEntry( currentQuery );
queryString += this.parseTerm( name, value, query, queryID );
}
queryString += "\n";
return queryString;
}
parseQueryJoins( query ) {
var queryString = "";
for(var c = 0; c< query.joins.length; c++) {
var join = query.joins[c];
join.parent = query;
join.queryID = c;
queryString += this.parseQuery( join, c );
}
return queryString;
}
parseQueryUnions( query, queryString ) {
var parsedUnionQuerys = new Array();
for (var i = 0; i < query.union.length; i++) {
var union = query.union[i];
union.root = true;
parsedUnionQuerys[i] = this.query( union, false );
}
if( parsedUnionQuerys.length > 0 ) {
return queryString = "SELECT * from ( " + parsedUnionQuerys.join(" \nUNION\n ") + ") ";
} else {
return queryString;
}
}
finalizeItems( query, items, queryID, stackedQuery ) {
var queryString = "";
for( var c = 0; c < items.length; c++){
var currentQuery = items[c];
var name = tools.getClassNameByEntry( currentQuery );
var value = tools.getObjectByEntry( currentQuery );
queryString += this.parseStacked( name, value, query, queryID, stackedQuery );
}
return queryString;
}
finalizeQuery( query, queryID ) {
var queryString = "";
if( tools.getClassName( query ) == "querySQL" && query.root ) {
var stackedQuery = new Object();
stackedQuery.where = new Array();
stackedQuery.groups = new Array();
stackedQuery.order = new Array();
this.stackQuerys( query, stackedQuery );
var items = Object.entries( stackedQuery );
queryString = this.finalizeItems( query, items, queryID, stackedQuery );
}
return queryString;
}
parseQuery( query, queryID = false ) {
var queryString = "";
if( query.filter && query.filter.search ) {
query.searchQuery = this.parseFilter( query );
//console.log("query.searchQuery", query.getClassName(), query.searchQuery, query);
if( tools.getClassName( query ) == "joinSQL" ) {
query.parent.searchQuery = query.searchQuery;
}
}
queryString += this.parseQueryItems( query, queryID );
queryString += this.parseQueryJoins( query );
queryString = this.parseQueryUnions( query, queryString );
queryString += this.finalizeQuery( query, queryID );
return queryString;
}
stackOrder( query, stackedQuery ) {
var order = query.order;
for ( var i = 0; i < order.length; i++ ) {
var orderStatement = new Object();
orderStatement.query = query;
orderStatement.columnName = order[ i ];
stackedQuery.order.push( orderStatement );
}
}
stackWhere( query, stackedQuery ) {
var where = query.where;
for (var i = 0; i < where.length; i++) {
var whereStatements = where[i];
whereStatements.query = query;
stackedQuery.where.push( whereStatements );
}
}
stackGroups( query, stackedQuery ) {
var groups = query.groups;
for (var i = 0; i < groups.length; i++) {
var group = groups[i];
group.query = query;
stackedQuery.groups.push( group );
}
}
stackQuerys( query, stackedQuery ) {
this.stackOrder( query, stackedQuery );
this.stackWhere( query, stackedQuery );
this.stackGroups( query, stackedQuery );
if( query.limit ) {
stackedQuery.limit = query.limit;
}
if( query.offset ) {
stackedQuery.offset = query.offset;
}
if( query.direction ) {
stackedQuery.direction = query.direction;
}
for (var i = 0; i < query.joins.length; i++) {
this.stackQuerys( query.joins[i], stackedQuery );
}
}
parseTerm( name, value, query, queryID ){
var queryString = "";
switch( name.toLowerCase() ) {
case "type":
queryString += this.parseType( value, query, queryID );
break;
case "columns":
queryString += this.parseColumns( value, query.type, query );
break;
case "table":
queryString += this.parseTableStatement( value, query );
break;
case "values":
queryString += this.parseValues( query );
break;
case "groupby":
queryString += this.parseGroupByStatement( value, query );
break;
case "count":
break;
}
return queryString;
}
parseGroupByStatement( value, query ){
var groups = value;
var groupStatements = new Array();
for (var i = 0; i < groups.length; i++) {
var currentGroup = groups[i];
var queryString = currentGroup.query.name + "." + currentGroup.columnName;
groupStatements.push( queryString );
}
if( groupStatements.length > 0 ) {
return " group by " + groupStatements.join(",");
} else {
return "";
}
}
parseStacked( name, value, query, queryID, stackedQuery ){
var queryString = "";
switch( name.toLowerCase() ) {
case "where":
queryString += this.parseWhereStatements( value, query );
break;
case "groups":
queryString += this.parseGroupByStatement( value, query );
break;
case "order":
queryString += this.parseOrderBy( value, query, stackedQuery );
break;
case "limit":
queryString += this.parseLimit( value );
break;
case "offset":
queryString += this.parseOffset( value );
break;
}
return queryString;
}
parseEndTerm( name, value, query, queryID ){
var queryString = "";
switch( name.toLowerCase() ) {
case "order":
queryString += this.parseOrderBy( value, query );
break;
}
return queryString;
}
parseLimit( limit ) {
var queryString = "";
if( limit ) {
console.log("limits", limit );
queryString += " LIMIT " + parseInt( limit );
}
return queryString;
}
parseOffset( offset ) {
var queryString = "";
if( offset ) {
queryString += " OFFSET " + parseInt( offset );
}
return queryString;
}
createOrderParts( orderStatements, value ) {
var orderParts = new Array();
for ( var i = 0; i < orderStatements.length; i++ ) {
var orderStatement = orderStatements[i];
var columnName = orderStatement.columnName;
var query = orderStatement.query;
orderParts.push( tools.htmlEntities( columnName ) );
}
return orderParts;
}
parseOrderBy( orderStatements, query, stackedQuery ) {
var orderParts = this.createOrderParts( orderStatements );
var queryString = "";
if( orderParts.length > 0 ) {
queryString += " ORDER BY " + orderParts.join(",");
//console.log("parsing order by:", stackedQuery );
if( query.direction ) {
//if( stackedQuery.direction.toLowerCase() == "asc" || stackedQuery.direction.toLowerCase() == "desc" ) {
queryString += " " + query.direction + " ";
//}
} else {
/*
if( stackedQuery.direction ) {
//if( stackedQuery.direction.toLowerCase() == "asc" || stackedQuery.direction.toLowerCase() == "desc" ) {
queryString += " " + stackedQuery.direction + " ";
//}
}
*/
}
}
return queryString;
}
getQuotationTerm( where ) {
var quotation = "";
if( where.quotation ) {
if( where.type == "LIKE" ) {
var quotation = "'%"
} else {
var quotation = "'"
}
}
if( typeof where.value == "boolean" ) {
var quotation = ""
if( where.value ) {
//where.value == 1;
} else {
//where.value == 0;
}
}
if( where.value == "false" ) {
var quotation = ""
//where.value == 0;
}
return quotation;
}
parseWhereStatement( where, ANDStatements, ORStatements ) {
var subQuery = where.query;
var tableName = subQuery.name + ".";
var quotation = this.getQuotationTerm( where );
var quotationEnd = tools.reverseString( quotation );
if( where.type != "" ) {
var whereString = " " + tableName + where.columnName + " " + where.type + " " + quotation + where.value + quotationEnd;
if( !where.or ) {
ANDStatements.push( whereString );
} else {
ORStatements.push( whereString );
}
}
}
parseWhereStatements( value, query ) {
var queryString = "";
if( value.length > 0 ) {
queryString += " where\n "
}
var ANDStatements = new Array();
var ORStatements = new Array();
for( var b = 0; b < value.length;b++ ){
var where = value[ b ];
this.parseWhereStatement( where, ANDStatements, ORStatements );
}
if( ORStatements.length > 0 ) {
ANDStatements.push( "(" + ORStatements.join("\n OR\n") + ")" ); // Custom for the logical operators
}
queryString += ANDStatements.join("\n AND\n");
//console.log( "query.searchQuery", query.searchQuery);
if( query.searchQuery ) {
queryString += "\n AND\n " + query.searchQuery;
}
return queryString;
}
stackJoinIDS( query ) {
var select_join_id = "";
for( var c = 0; c < query.joins.length; c++ ) {
var join = query.joins[c];
if( c == 0 ) {
select_join_id += "," + query.table + ".id as join_id";
} else {
select_join_id += "," + query.table + ".id as join_id_" + c;
}
}
return select_join_id;
}
parseColumns( value, queryType, query ) {
if( queryType == "select" ) {
if( value.length == 0 ) {
var select_join_id = this.stackJoinIDS( query );
return " * " + select_join_id + " ";
} else {
return " " + value.join(", ");
}
}
return "";
}
parseTableStatement( value, query ) {
var type = query.type;
if( type == "select" ) {
var select = " From " + value;
if( query.name ) {
select += " AS " + query.name + " ";
}
return select;
}
if( type != "join" ) {
return value;
}
return "";
}
parseInsertValues( query, values ) {
var valueString = new Array( values.length ).fill("?").join(", ");
var columnNames = query.getColumnNames();
return "(" + columnNames + ") VALUES (" + valueString + ")";
}
parseUpdateValues( values ) {
var updateArray = new Array();
for( var c = 0; c < values.length;c++ ) {
var column = values[c];
updateArray[c] = column.name + " = ?";
}
if( values.length == 0 ) {
return "";
}
return " SET " + updateArray.join(", ");
}
parseValues( query ) {
var values = query.values;
var type = query.type.toLowerCase();
switch( type ) {
case "insert":
return this.parseInsertValues( query, values );
break;
case "update":
return this.parseUpdateValues( values );
break;
default:
return "";
}
}
parseType( value, query, queryID ) {
switch( value.toLowerCase() ) {
case "select":
return value;
break;
case "count":
return "select count (*) from ";
break;
case "insert":
return "Insert into ";
break;
case "update":
return "update ";
break;
case "delete":
return "DELETE FROM ";
break;
case "join":
if( query.parent ) {
return this.queryJoin( query, "", queryID, query.parent );
}
break;
}
}
queryJoin( join, queryString, joinID, parentTable ) {
if( !parentTable.name ) {
parentTable.name = parentTable.table;
}
var left = join.left;
var right = join.right;
if( !right.table ) {
right.table = parentTable.name;
}
if( !left.column ) {
left.column = "id";
}
var joinType = "INNER";
if( join.joinType ) {
joinType = join.joinType;
}
var table;
if( join.table ) {
table = join.table;
} else {
table = left.table;
}
if( left.table && left.column && right.table && right.column ) {
var ON = " \n ON " + left.table + "." + left.column + " = " + right.table + "." + right.column;
} else {
var ON = "";
}
var AS = " as " + join.name;
return " " + joinType + " JOIN " + table + AS + ON;
}
subQuery( join, queryString, name ) {
var querys = join.querys;
for(var c = 0; c<querys.length;c++) {
var query = querys[c];
query.table = name;
query.parent = join;
queryString += this.parseQuery( query );
}
return queryString;
}
serializeColumnCreate( columns ){
if( !columns ) {
return false;
}
return columns.map( function( column ){
if( !column.constraints ) {
column.constraints = new Array();
}
return column.name + " " + column.type + " " + column.constraints.join(" ");
} ).join(", ");
}
createColumnQuery( columnName, datatype = "TEXT", constaints = new Array() ) {
return columnName + " " + datatype + " " + constaints.join(" ");
}
tableExists( name ) {
var query = this.database.prepare( "SELECT name FROM sqlite_master WHERE type='table' AND name='" + name + "' " );
return query.pluck().get();
}
createTable( name, columns, drop = false ) {
var columnString = this.serializeColumnCreate( columns );
if( this.type == "create" || drop ) {
var sql = "DROP TABLE IF EXISTS " + name + ";";
console.log( "New table created", name );
this.database.prepare( sql ).run();
console.log( sql );
}
if( columnString ) {
sql = "CREATE TABLE IF NOT EXISTS " + name + " (" + columnString + ");";
} else {
sql = "CREATE TABLE IF NOT EXISTS " + name + " ( id INTEGER PRIMARY KEY );";
}
this.database.prepare( sql ).run();
return true;
}
hasChanged( columnName, datatype, tableName ) {
var search = this.findColumn( columnName, tableName );
if( search.length == 0 ) {
return true;
} else {
return false;
}
}
addColumnSimple( columnName, datatype, tableName ) {
var sql = "ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + datatype;
this.database.prepare( sql ).run();
}
addColumn( columnName, datatype, tableName, constaints = new Array() ) {
var search = this.findColumn( columnName, tableName );
if( search.length == 0 ) {
console.log("");
console.log("________________________________________________________________________");
console.log("");
console.log( " New column added" );
console.log("");
console.log(" table: ", tableName);
console.log(" columnName: ", columnName);
console.log( " Constraints: ", constaints.join(" ") )
console.log("");
console.log("________________________________________________________________________");
var sql = "ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + datatype;
this.registerColumn( columnName, tableName, datatype );
this.database.prepare( sql ).run();
}
}
registerColumn( columnName, tableName, datatype = DATATYPE.VARCHAR ) {
var query = new querySQL( );
query.type = "insert";
query.table = "tableColumns";
query.setValue( "tableName", tableName );
query.setValue( "columnName", columnName );
query.setValue( "datatype", datatype );
var result = this.query( query );
}
cleanTable() {
//var query = new querySQL( );
//query.type = "delete";
//query.table = name;
//query.find( "signed", false );
//this.query( query );
}
findColumn( columnName, tableName ) {
var query = new querySQL( );
query.type = "select";
query.table = "tableColumns";
query.find( "tableName", tableName );
query.find( "columnName", columnName );
var result = this.query( query );
return result;
}
}
var db = new databaseManager()
db.connect();
export default db;