UNPKG

@d3x0r/sack-gui

Version:

SACK abstraction library exposed to JS to provide low level system services.

366 lines (319 loc) 9.71 kB
// what does this do with database? // Mysql - // get table format... // like parse the creates statement... // or query information schema? import {sack} from "sack.vfs"; class Db2 extends sack.Sqlite { constructor(db) { super(db); } } class Sqlite { static loadSchema( db, table ) { if( db.provider !== 1 ) return MySQL.loadSchema( db, table ); const newtable = new Table(db, table, true); return newtable; } } class MySQL { static loadSchema( db, table ) { if( db.provider === 1 ) return Sqlite.loadSchema( db, table ); const newtable = new Table(db, table); return newtable; } } class Table { name = null; columns = []; cols = {}; indexes = []; keys = {}; fkeys = []; db = null; sqlite = false; constructor( db_, tableName, sqlite ) { this.db = db_; this.name = tableName; this.sqlite = sqlite; if( db_ ) if( sqlite ) this.loadSqliteColumns( db_ ) else this.loadColumns(db_); } has( col ) { return( col in this.cols ) } addColumn( name, type, extra ) { const t = type.split( '('); const p = ( t.length > 1 )?Number( t[1].slice( 0, -1 )):0; const newCol = new Column(); newCol.name = name; newCol.type = t[0]; newCol.precision = p; if( extra && extra.includes( "DEFAULT" ) ) { newCol.default = tokenAfter( extra, "DEFAULT" ); } if( extra && (extra.includes( "AUTO_INCREMENT" ) || extra.includes( "auto_increment" )) ) { newCol.auto_increment = true; newCol.primary_key = true; } if( extra && !extra.includes( "NOT NULL" ) ) newCol.nullable = false; this.columns.push( newCol ); this.cols[name] = newCol; this.db.do( `ALTER TABLE ${this.name} ADD COLUMN ${newCol.name} ${type} ${extra || ""}` ); } loadColumns(db) { let now = Date.now(); const {db:dbname} = this.sqlite?"":db.do( "select DATABASE() db" )[0]; //console.log( Date.now()-now, "selected Db" ); now = Date.now(); const cols = db.do( "select COLUMN_NAME,COLUMN_TYPE,DATA_TYPE,COLUMN_DEFAULT,COLUMN_KEY,NUMERIC_PRECISION,NUMERIC_SCALE,IS_NULLABLE,CHARACTER_MAXIMUM_LENGTH,EXTRA from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME=? and TABLE_SCHEMA=? ORDER BY ORDINAL_POSITION", this.name, dbname ); //console.log( Date.now()-now, "selected 1" ); now = Date.now(); for( let col of cols ) { const newcol = new Column(); const t = col.DATA_TYPE.split( '('); newcol.name = col.COLUMN_NAME; newcol.numeric_scale = col.NUMERIC_SCALE || 0; newcol.string_length = col.CHARACTER_MAXIMUM_LENGTH || 0; newcol.precision = (col.NUMERIC_PRECISION+1) || 0; newcol.nullable = col.IS_NULLABLE==="YES"; //console.log( "dflt?", t, col ); switch( col.DATA_TYPE ) { default: console.log( "Unhandled column type to convert:", t[0] ); newcol.default = col.COLUMN_DEFAULT; break; case 'varchar': case 'char': case 'text': case 'mediumtext': case 'longtext': case 'tinyblob': case 'datetime': case 'date': case 'json': case 'timestamp': newcol.default = col.COLUMN_DEFAULT; break; case 'int': case 'decimal': case 'bigint': case 'tinyint': case 'double': case 'float': //console.log( "numeric default?", typeof( col.COLUMN_DEFAULT ), col.COLUMN_DEFAULT ); if( !col.COLUMN_DEFAULT ) newcol.default = null; else newcol.default = Number(col.COLUMN_DEFAULT); break; } newcol.type = t[0]; if( col.COLUMN_KEY==="PRI" ) newcol.primary_key = true; if( col.EXTRA ==="auto_increment" ||col.EXTRA ==="AUTO_INCREMENT") newcol.auto_increment = true //console.log( "COL:", newcol ); this.columns.push( newcol ); this.cols[newcol.name] = newcol; //const //col.COLUMN_NAME //col.DATA_TYPE //col.COLUMN_TYPE // includes DATA_TYPE + (precision) } const keys = db.do( "select INDEX_NAME,NON_UNIQUE,COLUMN_NAME from INFORMATION_SCHEMA.STATISTICS where TABLE_NAME=? and TABLE_SCHEMA=?", this.name, dbname ); //console.log( Date.now()-now, "selected 2" ); now = Date.now(); for( let key of keys ) { const index = this.keys[key.INDEX_NAME] || new Index(); //console.log( "index?", index ); index.name = key.INDEX_NAME; index.unique = !key.NON_UNIQUE; index.columns.push( key.COLUMN_NAME ); if( !this.keys[key.INDEX_NAME] ) { this.keys[key.INDEX_NAME] = index; this.indexes.push( index ); } else { } } const fkeys = db.do( " \ SELECT TABLE_NAME,COLUMN_NAME,CONSTRAINT_NAME, REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME \ FROM \ INFORMATION_SCHEMA.KEY_COLUMN_USAGE \ WHERE \ REFERENCED_TABLE_SCHEMA = ? AND \ TABLE_NAME = ? ", dbname, this.name ); //console.log( 'foreign keys:', fkeys ); for( let fkey of fkeys ) { const fk = new ForeignKey(); fk.name = fkey.CONSTRAINT_NAME; fk.refTable = fkey.REFERENCED_TABLE_NAME; fk.refColumn = fkey.REFERENCED_COLUMN_NAME; fk.srcColumn = fkey.COLUMN_NAME; this.fkeys.push( fk ); //console.log( "again:", fk ); } } loadSqliteColumns(db) { //const cols_ = db.do( "select * from sqlite_schema" ); const cols = db.do( `PRAGMA table_info(${this.name})` ); //const cols2 = db.do( `PRAGMA table_info('stuffkey')` ); /* { type: 'table', name: 'table1', tbl_name: 'table1', rootpage: 2, sql: 'CREATE TABLE `table1` (`id` INTEGER PRIMARY KEY,`stuff` int(11))' } { type: 'index', name: 'stuffkey', tbl_name: 'table2', rootpage: 4, sql: "CREATE INDEX 'stuffkey' ON 'table2'('stuff')" } { cid: 0, name: 'id', type: 'INTEGER', notnull: 0, dflt_value: null, pk: 1 }, */ //console.log( "Sqlite info:", cols_ ); //console.log( "Sqlite info:", cols ); //console.log( "Sqlite info:", cols2 ); //process.exit(0) for( let col of cols ) { const newcol = new Column(); newcol.name = col.name; const t = col.type.split( '('); if( t.length > 1 ) { newcol.precision = Number(t[1].slice( 0, -1 )); } newcol.nullable = !col.notnull; switch( t[0] ) { default: console.log( "Unhandled column type to convert:", t[0] ); newcol.default = col.dflt_value; break; case 'varchar': case 'char': case 'datetime': newcol.default = col.dflt_value; break; case 'int': case 'decimal': case 'bigint': if( !col.dflt_value ) newcol.default = null; else newcol.default = Number(col.dflt_value); break; } newcol.type = t[0]; newcol.type_ = col.type; newcol.primary_key = !!col.pk; // if( col.EXTRA ==="auto_incrememnt" ) // newcol.auto_increment = true //console.log( "COL:", newcol ); this.columns.push( newcol ); this.cols[newcol.name] = newcol; //const //col.COLUMN_NAME //col.DATA_TYPE //col.COLUMN_TYPE // includes DATA_TYPE + (precision) } /* const keys = db.do( "select * from INFORMATION_SCHEMA.STATISTICS where TABLE_NAME=? and TABLE_SCHEMA=DATABASE()", this.name ); for( let key of keys ) { const index = this.keys[key.INDEX_NAME] || new Index(); //console.log( "index?", index ); index.name = key.INDEX_NAME; index.unique = !key.NON_UNIQUE; index.columns.push( key.COLUMN_NAME ); if( !this.keys[key.INDEX_NAME] ) { this.keys[key.INDEX_NAME] = index; this.indexes.push( index ); } else { } } */ } } class Index { name = null; unique = false; columns = []; } class Column { name = null; type = null; // SQL expression for type type_ = null; // sqlite idea of type precision = 0; numeric_scale = 0; // used with decimal types string_length = 0; default = null; nullable = false; auto_increment = false; primary_key = false; } class ForeignKey { name = null; refTable = null; refColumn = null; srcColumn = null; } //console.log( "TeST:", tokenAfter( "DEFAULT '0'", "DEFAULT" ) ); //console.log( "TeST:", tokenAfter( "DEFAULT '0' NOT NULL", "DEFAULT" ) ); //console.log( "TeST:", tokenAfter( "DEFAULT 123", "DEFAULT" ) ); //console.log( "TeST:", tokenAfter( "DEFAULT 123 NOT NULL", "DEFAULT" ) ); function tokenAfter( s, token ) { let at = s.indexOf( token ); if( s < 0 ) throw new Error( "This expects you already now the token is in the string." ); let from = 0; at += token.length; while( s[at] == ' ' ) at++; if( s[at] == "'" ) { from = at; at++; while( s[at] != "'" ){ if( at >= s.length ) throw new Error( "invalid SQL token after:", token ); at++; } } else { from = at; while( s[at] != " " && at < s.length ) at++; } return s.substr( from, at-from+1 ); } class Db { db = null; MySQL=MySQL; Sqlite=Sqlite; constructor() { } getSqlDateTime(date) { if( date.getTime() === -62167219200000 ) return "0000-00-00 00:00:00"; const yr = date.getUTCFullYear(); const mo = date.getUTCMonth()+1; const dy = date.getUTCDate(); const h = date.getUTCHours(); const m = date.getUTCMinutes(); const s = date.getUTCSeconds(); const string = yr.toString() + '-'+mo.toString().padStart(2,"0")+'-'+dy.toString( ).padStart(2,"0") +" "+h.toString().padStart(2, '0') +":"+m.toString().padStart(2, '0') +":"+s.toString().padStart(2, '0') +"Z"; return string; } } const db = new Db(); //console.log( "Why isn't db good?", db ); export default db;