/* 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