You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1161 lines
33 KiB
1161 lines
33 KiB
/*
|
|
* Copyright (c)2012 Nicolas Cannasse
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
package sys.db;
|
|
|
|
|
|
import sys.db.Object;
|
|
import sys.db.Manager;
|
|
import sys.db.Types;
|
|
|
|
import sys.db.Custom;
|
|
import sys.db.TableInfos.TableType;
|
|
import sys.db.TableInfos.ManagerAccess;
|
|
#if neko
|
|
import neko.Lib;
|
|
import neko.Web;
|
|
#elseif php
|
|
import php.Lib;
|
|
import php.Web;
|
|
#end
|
|
|
|
@:access(sys.db.Manager)
|
|
class Admin {
|
|
|
|
var style : AdminStyle;
|
|
var hasSyncAction : Bool;
|
|
var countCache : Map<String,Bool>;
|
|
public var allowDrop : Bool;
|
|
public var default_rights : RightsInfos;
|
|
public var maxUploadSize : Int;
|
|
public var maxInstanceCount : Int;
|
|
|
|
public function new() {
|
|
maxInstanceCount = 100;
|
|
maxUploadSize = 1000000;
|
|
allowDrop = false;
|
|
countCache = new Map();
|
|
default_rights = {
|
|
can : {
|
|
insert : true,
|
|
delete : true,
|
|
modify : true,
|
|
truncate : false,
|
|
},
|
|
invisible : [],
|
|
readOnly : [],
|
|
};
|
|
}
|
|
|
|
function execute(sql) {
|
|
return Manager.cnx.request(sql);
|
|
}
|
|
|
|
function request( t : TableInfos, sql ) {
|
|
return Manager.cnx.request(sql);
|
|
}
|
|
|
|
function boolResult(sql) {
|
|
try {
|
|
execute(sql);
|
|
return true;
|
|
} catch( e : Dynamic ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getTables():Array<sys.db.TableInfos> {
|
|
var tables = new Array();
|
|
var classes = Lib.getClasses();
|
|
crawl(tables, classes);
|
|
tables.sort(function(t1,t2) { return if( t1.name > t2.name ) 1 else if( t1.name < t2.name ) -1 else 0; });
|
|
return tables;
|
|
}
|
|
|
|
function has<T>( a : { function iterator() : Iterator<T>; }, v : T ) {
|
|
for( x in a.iterator() )
|
|
if( x == v )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Filter classes who sublass sys.db.Object and get TableInfos
|
|
*/
|
|
function crawl(tables : Array<TableInfos>,classes : Dynamic) {
|
|
for( cname in Reflect.fields(classes) ) {
|
|
|
|
var v : Dynamic = Reflect.field(classes,cname);
|
|
var c = cname.charAt(0);
|
|
if( c >= "a" && c <= "z" ) {
|
|
//explore sub packages
|
|
crawl(tables,v);
|
|
continue;
|
|
}
|
|
#if !php
|
|
if( haxe.rtti.Meta.getType(v).rtti == null )
|
|
continue;
|
|
#end
|
|
var s = Type.getSuperClass(v);
|
|
while( s != null ) {
|
|
if( s == sys.db.Object ) {
|
|
tables.push(new TableInfos(Type.getClassName(v)));
|
|
break;
|
|
}
|
|
s = Type.getSuperClass(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function index( ?errorMsg ) {
|
|
style.begin("Tables");
|
|
style.beginForm("doSync");
|
|
style.beginTable();
|
|
var sync = false;
|
|
var allTables = new List();
|
|
var rq = execute(TableInfos.allTablesRequest());
|
|
for( r in rq ){
|
|
if (rq.getResult(0) == null) continue;
|
|
allTables.add(rq.getResult(0));
|
|
}
|
|
var windows = Sys.systemName() == "Windows";
|
|
for( t in getTables() ) {
|
|
var rights = getRights(createInstance(t));
|
|
style.beginLine(true);
|
|
style.text(t.name);
|
|
style.nextRow();
|
|
if( !boolResult(t.existsRequest()) ) {
|
|
style.linkConfirm(t.className+"/doCreate","create");
|
|
style.text("Table is Missing !");
|
|
} else {
|
|
if( needSync(t) )
|
|
sync = true;
|
|
if( rights.can.insert )
|
|
style.link(t.className+"/insert","insert");
|
|
style.nextRow();
|
|
style.link(t.className+"/search","search");
|
|
if( rights.can.truncate )
|
|
style.linkConfirm(t.className+"/doCleanup","cleanup");
|
|
if( allowDrop ) {
|
|
style.nextRow();
|
|
style.linkConfirm(t.className+"/doDrop","drop");
|
|
}
|
|
}
|
|
style.endLine();
|
|
allTables.remove(t.name);
|
|
if( windows || TableInfos.OLD_COMPAT ) allTables.remove(t.name.toLowerCase());
|
|
}
|
|
style.endTable();
|
|
if( sync )
|
|
style.addSubmit("Synchronize Database",true);
|
|
style.endForm();
|
|
if( !allTables.isEmpty() ) {
|
|
style.beginList();
|
|
for( t in allTables ) {
|
|
style.beginItem();
|
|
style.text('Table "$t" does not have any matching object');
|
|
style.endItem();
|
|
}
|
|
style.endList();
|
|
}
|
|
if( errorMsg != null )
|
|
style.error(errorMsg);
|
|
style.end();
|
|
}
|
|
|
|
function isBinary(t) {
|
|
return switch( t ) {
|
|
case DBinary, DSmallBinary, DLongBinary, DBytes(_): true;
|
|
default: false;
|
|
};
|
|
}
|
|
|
|
function canDisplay( m : ManagerAccess ) {
|
|
var c = countCache.get(m.table_name);
|
|
if( c != null )
|
|
return c;
|
|
c = execute(TableInfos.countRequest(m,maxInstanceCount)).length < maxInstanceCount;
|
|
countCache.set(m.table_name,c);
|
|
return c;
|
|
}
|
|
|
|
function inputField( table : TableInfos, f, id : String, readonly, ?defval : Dynamic, ?rawValue : Bool ) {
|
|
var prim = has(table.primary,f.name);
|
|
var insert = (id == null);
|
|
for( r in table.relations )
|
|
if( r.key == f.name ) {
|
|
var values = null;
|
|
if( canDisplay(r.manager) || r.className == "db.File" )
|
|
values = r.manager.all(false).map(function(d) {
|
|
return {
|
|
id : Std.string(Reflect.field(d,r.manager.table_keys[0])),
|
|
str : d.toString()
|
|
};
|
|
});
|
|
var cname = r.className.substr(0,3) == "db." ? r.className.substr(3) : r.className;
|
|
style.choiceField(
|
|
r.prop,
|
|
values,
|
|
Std.string(defval),
|
|
cname+"/edit/",
|
|
!insert && (prim || readonly),
|
|
r.className == "db.File"
|
|
);
|
|
return;
|
|
}
|
|
if( defval != null && !rawValue ) {
|
|
switch( f.type ) {
|
|
case DEncoded:
|
|
defval = Std.parseInt(Std.string(defval));
|
|
defval = Id.decode(defval);
|
|
case DSerialized:
|
|
defval = new Serialized(defval).escape();
|
|
|
|
case DNekoSerialized:
|
|
#if neko
|
|
var v = try haxe.Serializer.run(Lib.localUnserialize(defval)) catch( e : Dynamic ) ("ERROR : " + Std.string(e));
|
|
defval = new Serialized(v).escape();
|
|
#else
|
|
throw "DNekoSerialized is only available on neko target";
|
|
#end
|
|
case DData:
|
|
#if haxe3
|
|
var str = try haxe.Serializer.run((untyped table.manager).doUnserialize(f.name, defval)) catch( e : Dynamic ) ("ERROR : " + Std.string(e));
|
|
#else
|
|
var str = defval.toString();
|
|
#end
|
|
defval = new Serialized(str).escape();
|
|
default:
|
|
}
|
|
}
|
|
if( isBinary(f.type) )
|
|
style.binField(f.name,table.nulls.exists(f.name),defval,if( insert ) null else function() { return table.name+"/doDownload/"+id+"/"+f.name; });
|
|
else if( insert && readonly )
|
|
return;
|
|
else if( !insert && (prim || readonly) )
|
|
style.infoField(f.name,defval);
|
|
else
|
|
style.inputField(f.name,f.type,table.nulls.exists(f.name),defval);
|
|
}
|
|
|
|
/**
|
|
* Prints an insert form
|
|
*/
|
|
function insert(table : TableInfos, ?params : Map<String,String>, ?error : String, ?errorMsg : String ) {
|
|
var binary = false;
|
|
for( f in table.fields )
|
|
if( isBinary(f.type) ) {
|
|
binary = true;
|
|
break;
|
|
}
|
|
style.begin("Insert new "+table.name);
|
|
style.beginForm(table.className+"/doInsert",binary,table.name);
|
|
var rights = getRights(table);
|
|
for( f in table.fields ) {
|
|
if( f.name == error ) {
|
|
style.errorField((errorMsg == null) ? "Invalid format" : errorMsg);
|
|
errorMsg = null;
|
|
}
|
|
if( has(rights.invisible,f.name) )
|
|
continue;
|
|
var readonly = has(rights.readOnly,f.name);
|
|
inputField(table,f,null,readonly,if( params == null ) null else params.get(f.name), params != null );
|
|
}
|
|
style.addSubmit("Insert");
|
|
style.addSubmit("Insert New",null,false,"__new");
|
|
style.endForm();
|
|
if( errorMsg != null )
|
|
style.error(errorMsg);
|
|
style.end();
|
|
}
|
|
|
|
function updateField( fname : String, v : String, ftype : TableType, table : TableInfos ) : Dynamic {
|
|
switch( ftype ) {
|
|
case DId, DUId, DBigId:
|
|
return null;
|
|
case DDate:
|
|
var d = if( v == "NOW" || v == "NOW()" ) Date.now() else try Date.fromString(v) catch( e : Dynamic ) null;
|
|
if( d == null )
|
|
return null;
|
|
try d.toString() catch( e : Dynamic ) return null;
|
|
return d;
|
|
case DDateTime, DTimeStamp:
|
|
var d = if( v == "NOW" || v == "NOW()" ) Date.now() else try Date.fromString(v) catch( e : Dynamic ) null;
|
|
if( d == null )
|
|
return null;
|
|
try d.toString() catch( e : Dynamic ) return null;
|
|
return d;
|
|
case DInt:
|
|
if( v == "" ) return 0;
|
|
return Std.parseInt(v);
|
|
case DUInt, DFlags(_):
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseInt(v);
|
|
if( i < 0 )
|
|
return null;
|
|
return i;
|
|
case DTinyInt:
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseInt(v);
|
|
if( i < -128 || i > 127 )
|
|
return null;
|
|
return i;
|
|
case DTinyUInt:
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseInt(v);
|
|
if( i < 0 || i > 255 )
|
|
return null;
|
|
return i;
|
|
case DSmallInt:
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseInt(v);
|
|
if( i < -32768 || i > 32767 )
|
|
return null;
|
|
return i;
|
|
case DSmallUInt:
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseInt(v);
|
|
if( i < 0 || i > 65535 )
|
|
return null;
|
|
return i;
|
|
case DMediumInt:
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseInt(v);
|
|
if( i < -8388608 || i > 8388607 )
|
|
return null;
|
|
return i;
|
|
case DMediumUInt:
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseInt(v);
|
|
if( i < 0 || i > 16777215 )
|
|
return null;
|
|
return i;
|
|
case DBigInt:
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseFloat(v);
|
|
if( i == null || i%1 != 0 || i < -9223372036854775808.0 || i > 9223372036854775807.0 )
|
|
return null;
|
|
return i;
|
|
case DFloat, DSingle:
|
|
if( v == "" ) return 0;
|
|
var fl = Std.parseFloat(v);
|
|
if( Math.isNaN(fl) )
|
|
return null;
|
|
return fl;
|
|
case DString(n):
|
|
if( v.length > n )
|
|
return null;
|
|
return v;
|
|
case DTinyText:
|
|
if( v.length > 255 )
|
|
return null;
|
|
return v;
|
|
case DSmallText, DSmallBinary:
|
|
if( v.length > 0xFFFF )
|
|
return null;
|
|
return v;
|
|
case DText, DBinary:
|
|
if( v.length > 0xFFFFFF )
|
|
return null;
|
|
return v;
|
|
case DBytes(n):
|
|
if( v.length > n )
|
|
return null;
|
|
return v;
|
|
case DLongBinary:
|
|
return v;
|
|
case DBool:
|
|
return (v == "true");
|
|
case DEncoded:
|
|
if( v == "" ) return null;
|
|
return try Id.encode(v) catch( e : Dynamic ) null;
|
|
case DSerialized:
|
|
return new Serialized(v).encode();
|
|
case DNekoSerialized:
|
|
#if neko
|
|
var str = new Serialized(v).encode();
|
|
var val = neko.Lib.serialize(haxe.Unserializer.run(str));
|
|
return val;
|
|
#else
|
|
throw "DNekoSerialized is only available on neko target";
|
|
return null;
|
|
#end
|
|
case DData:
|
|
var s = new Serialized(v).encode();
|
|
if( s.length > 0xFFFFFF )
|
|
return null;
|
|
#if haxe3
|
|
return (untyped table.manager).doSerialize(fname, haxe.Unserializer.run(s)) ;
|
|
#else
|
|
return haxe.io.Bytes.ofString(s);
|
|
#end
|
|
case DEnum(e):
|
|
if( v == "" ) return 0;
|
|
var i = Std.parseInt(v);
|
|
var ev = Type.resolveEnum(e);
|
|
if( i < 0 || (ev != null && i >= Type.getEnumConstructs(ev).length) )
|
|
return null;
|
|
return i;
|
|
case DNull, DInterval:
|
|
throw "assert";
|
|
}
|
|
}
|
|
|
|
function createInstance( table : TableInfos ) : Object {
|
|
var c = Type.createEmptyInstance(table.cl);
|
|
#if !neko
|
|
untyped if( c._manager == null ) c._manager = c.__getManager();
|
|
#end
|
|
return c;
|
|
}
|
|
|
|
function getRights( ?t : Object, ?table : TableInfos ) : RightsInfos {
|
|
if( t == null )
|
|
t = createInstance(table);
|
|
if( untyped t.dbRights == null )
|
|
return default_rights;
|
|
var r : RightsInfos = untyped t.dbRights();
|
|
if( r == null )
|
|
return default_rights;
|
|
if( r.can == null )
|
|
r.can = default_rights.can;
|
|
return r;
|
|
}
|
|
|
|
function getSInfos( t : Object ) : SearchInfos {
|
|
if( untyped t.dbSearch == null )
|
|
return null;
|
|
return untyped t.dbSearch();
|
|
}
|
|
|
|
/**
|
|
* insert a new object in a table
|
|
*/
|
|
function doInsert( table : TableInfos, params : Map<String,String> ) {
|
|
var inst = createInstance(table);
|
|
updateParams(table,params);
|
|
for( f in table.fields ) {
|
|
var v = params.get(f.name);
|
|
#if (haxe_ver < 3.2)
|
|
var fieldName = f.name;
|
|
#else
|
|
var fieldName = Manager.getFieldName({name: f.name, t: f.type, isNull: table.nulls.exists(f.name)});
|
|
#end
|
|
if( v == null ) {
|
|
if( table.nulls.exists(f.name) )
|
|
Reflect.setField(inst,fieldName,null);
|
|
else
|
|
for( r in table.relations )
|
|
if( f.name == r.key ) {
|
|
insert(table,params,f.name);
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
var msg = null;
|
|
var v = try updateField(f.name, v, f.type, table) catch( err : String ) { msg = err; null; };
|
|
if( v == null ) {
|
|
// loop in case of error Invalid_format
|
|
insert(table,params,f.name,msg);
|
|
return;
|
|
}
|
|
Reflect.setField(inst,fieldName,v);
|
|
}
|
|
if( table.primary.length == 1 && Reflect.field(inst,table.primary.first()) == 0 )
|
|
Reflect.deleteField(inst,table.primary.first());
|
|
try {
|
|
if( !getRights(inst).can.insert )
|
|
throw "Can't insert";
|
|
if(inst == null)
|
|
throw "instance is null";
|
|
inst.insert();
|
|
log("Inserted "+table.name+" "+table.identifier(inst));
|
|
} catch( e : Dynamic ) {
|
|
insert(table,params,null,Std.string(e));
|
|
return;
|
|
}
|
|
|
|
//display a blank form to insert a new object
|
|
if( params.exists("__new") ) {
|
|
insert(table,params);
|
|
return;
|
|
}
|
|
style.goto(table.className+"/edit/"+table.identifier(inst));
|
|
}
|
|
|
|
function doCreate(table : TableInfos) {
|
|
try {
|
|
execute(table.createRequest(false));
|
|
style.goto("");
|
|
} catch( e : Dynamic ) {
|
|
index(Std.string(e));
|
|
}
|
|
}
|
|
|
|
function doDrop(table : TableInfos) {
|
|
if( !allowDrop )
|
|
throw "Drop not allowed";
|
|
execute(table.dropRequest());
|
|
style.goto("");
|
|
}
|
|
|
|
function doCleanup( table : TableInfos ) {
|
|
if( !getRights(table).can.truncate )
|
|
throw "Can't cleanup";
|
|
execute(table.truncateRequest());
|
|
style.goto("");
|
|
}
|
|
|
|
function edit( table : TableInfos, id : String, ?params : Map<String,String>, ?error : String, ?errorMsg : String ) {
|
|
var obj = table.fromIdentifier(id);
|
|
var objStr = try Std.string(obj) catch( e : Dynamic ) "#"+id;
|
|
style.begin("Edit "+table.name+" "+objStr);
|
|
if( obj == null ) {
|
|
style.error("This object does not exists");
|
|
style.end();
|
|
return;
|
|
}
|
|
var binary = false;
|
|
for( f in table.fields )
|
|
if( isBinary(f.type) ) {
|
|
binary = true;
|
|
break;
|
|
}
|
|
style.beginForm(table.className+"/doEdit/"+id,binary,table.name);
|
|
var rights = getRights(obj);
|
|
var hasBinary = false;
|
|
for( f in table.fields ) {
|
|
if( f.name == error ) {
|
|
style.errorField((errorMsg == null) ? "Invalid format" : errorMsg);
|
|
errorMsg = null;
|
|
}
|
|
if( has(rights.invisible,f.name) )
|
|
continue;
|
|
var readonly = has(rights.readOnly,f.name);
|
|
inputField(table,f,id,readonly,if( params == null ) Reflect.field(obj,f.name) else params.get(f.name), params != null);
|
|
if( !readonly && isBinary(f.type) )
|
|
hasBinary = true;
|
|
}
|
|
if( rights.can.modify ) {
|
|
style.addSubmit("Modify");
|
|
if( hasBinary )
|
|
style.addSubmit("Upload",null,null,"__upload");
|
|
}
|
|
style.addSubmit("Cancel",table.className+"/edit/"+id);
|
|
if( rights.can.delete )
|
|
style.addSubmit("Delete",table.className+"/doDelete/"+id,true);
|
|
style.endForm();
|
|
if( errorMsg != null )
|
|
style.error(errorMsg);
|
|
style.end();
|
|
}
|
|
|
|
function doEdit( table : TableInfos, id : String, params : Map<String,String> ) {
|
|
var inst = table.fromIdentifier(id);
|
|
if( inst == null ) {
|
|
style.goto(table.className+"/edit/"+id);
|
|
return;
|
|
}
|
|
updateParams(table,params);
|
|
var rights = getRights(inst);
|
|
var binaries = new List();
|
|
for( f in table.fields ) {
|
|
if( has(rights.readOnly,f.name) || has(rights.invisible,f.name) )
|
|
continue;
|
|
#if (haxe_ver < 3.2)
|
|
var fieldName = f.name;
|
|
#else
|
|
var fieldName = Manager.getFieldName({name: f.name, t: f.type, isNull: table.nulls.exists(f.name)});
|
|
#end
|
|
var v = params.get(f.name);
|
|
if( v == null ) {
|
|
if( table.nulls.exists(f.name) )
|
|
Reflect.setField(inst,fieldName,null);
|
|
continue;
|
|
}
|
|
var msg = null;
|
|
var v = try updateField(f.name, v, f.type, table) catch( err : Dynamic ) { msg = err; null; };
|
|
if( v == null ) {
|
|
// insert ID into params
|
|
if( table.primary != null ) {
|
|
for( p in table.primary )
|
|
params.set(p,Reflect.field(inst,p));
|
|
}
|
|
for( f in rights.readOnly )
|
|
params.set(f, Reflect.field(inst, f));
|
|
// error Invalid_format
|
|
edit(table,id,params,f.name, msg);
|
|
return;
|
|
}
|
|
var bin = isBinary(f.type);
|
|
if( Std.is(v,String) && v == "" && bin )
|
|
continue;
|
|
Reflect.setField(inst,fieldName,v);
|
|
if( bin )
|
|
binaries.add({ name : f.name, value : v });
|
|
}
|
|
try {
|
|
if( !rights.can.modify )
|
|
throw "Can't modify";
|
|
if( params.exists("__upload") )
|
|
request(table,table.updateFields(inst,binaries));
|
|
else {
|
|
inst.update();
|
|
log("Updated "+table.name+" "+table.identifier(inst));
|
|
}
|
|
} catch( e : Dynamic ) {
|
|
edit(table,id,params,null,Std.string(e));
|
|
return;
|
|
}
|
|
style.goto(table.className+"/edit/"+table.identifier(inst));
|
|
}
|
|
|
|
/**
|
|
* Sync some of the object fields from the web request
|
|
*/
|
|
function updateParams( table : TableInfos, params : Map<String,String> ) {
|
|
var tmp = Web.getMultipart(maxUploadSize);
|
|
for( k in tmp.keys() )
|
|
params.set(k,tmp.get(k));
|
|
for( r in table.relations ) {
|
|
var p = params.get(r.prop);
|
|
params.remove(r.prop);
|
|
if( p == null || p == "" )
|
|
continue;
|
|
params.set(r.key,p);
|
|
params.remove(r.prop+"__data");
|
|
params.set(r.key+"__data","on");
|
|
}
|
|
for( f in table.fields ) {
|
|
switch( f.type ) {
|
|
case DFlags(flags,_):
|
|
var vint = 0;
|
|
for( i in 0...flags.length )
|
|
if( params.exists(f.name + "_" + flags[i]) )
|
|
vint |= 1 << i;
|
|
if( table.nulls.exists(f.name) && !params.exists(f.name+"__data") && vint == 0 ) {
|
|
params.remove(f.name);
|
|
continue;
|
|
}
|
|
params.set(f.name, Std.string(vint));
|
|
params.set(f.name + "__data", "true");
|
|
default:
|
|
}
|
|
if( table.nulls.exists(f.name) && !params.exists(f.name+"__data") && (params.get(f.name) == "" || params.get(f.name) == null) ) {
|
|
params.remove(f.name);
|
|
continue;
|
|
}
|
|
if( f.type == DBool ) {
|
|
var v = params.exists(f.name);
|
|
params.set(f.name,if( v ) "true" else "false");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes a record
|
|
*/
|
|
function doDelete( table : TableInfos, id : String ) {
|
|
var inst = table.fromIdentifier(id);
|
|
if( inst == null ) {
|
|
style.goto(table.className+"/edit/"+id);
|
|
return;
|
|
}
|
|
if( !getRights(inst).can.delete ) {
|
|
edit(table,id,null,null,"Can't Delete");
|
|
return;
|
|
}
|
|
inst.delete();
|
|
log("Deleted "+table.name+" "+id);
|
|
style.goto("");
|
|
}
|
|
|
|
function doDownload( table : TableInfos, id : String, field : String ) {
|
|
var inst = table.fromIdentifier(id);
|
|
if( inst == null ) {
|
|
style.goto(table.className+"/edit/"+id);
|
|
return;
|
|
}
|
|
var rights = getRights(inst);
|
|
var f = table.hfields.get(field);
|
|
var data : String = Reflect.field(inst,field);
|
|
if( has(rights.invisible,field) || data == null || !isBinary(f) ) {
|
|
edit(table,id,null,null,"Can't Download data");
|
|
return;
|
|
}
|
|
Web.setHeader("Content-Type","text/binary");
|
|
Web.setHeader("Content-Length",Std.string(data.length));
|
|
Sys.print(data);
|
|
}
|
|
|
|
function search( table : TableInfos, params : Map<String,String> ) {
|
|
style.begin("Search "+table.name);
|
|
|
|
var pagesize = 30;
|
|
var page = Std.parseInt(params.get("__p"));
|
|
var order = params.get("__o");
|
|
if( page == null )
|
|
page = 0;
|
|
params.remove("__p");
|
|
params.remove("__o");
|
|
|
|
// save params for later usage
|
|
var paramsStr = "";
|
|
for( p in params.keys() ) {
|
|
var v = params.get(p);
|
|
paramsStr += p+"="+StringTools.urlEncode(v)+";";
|
|
}
|
|
|
|
// set nullable for all types which can be searched with empty string or no values
|
|
for( f in table.fields )
|
|
switch( f.type ) {
|
|
case DBool, DString(_), DTinyText, DText, DSmallText, DFlags(_):
|
|
table.nulls.set(f.name,true);
|
|
default:
|
|
}
|
|
|
|
updateParams(table,params);
|
|
|
|
// remove not null fields that have not been completed
|
|
for( f in table.fields )
|
|
if( !table.nulls.exists(f.name) && params.get(f.name) == "" )
|
|
params.remove(f.name);
|
|
|
|
var rights = getRights(table);
|
|
for( f in rights.invisible )
|
|
params.remove(f);
|
|
|
|
var results = table.fromSearch(params,order,page*pagesize,pagesize+1);
|
|
var hasNext = false;
|
|
if( results.length > pagesize ) {
|
|
results.remove(results.last());
|
|
hasNext = true;
|
|
}
|
|
|
|
var sinfos : SearchInfos = getSInfos(createInstance(table));
|
|
var fields;
|
|
if( sinfos != null && sinfos.fields != null )
|
|
fields = sinfos.fields;
|
|
else {
|
|
fields = new Array();
|
|
for( f in table.fields ) {
|
|
var bad = false;
|
|
for( r in table.relations )
|
|
if( r.key == f.name && r.className == "db.File" ) {
|
|
bad = true;
|
|
break;
|
|
}
|
|
if( bad )
|
|
continue;
|
|
fields.push(f.name);
|
|
}
|
|
}
|
|
|
|
style.beginForm(table.className+"/search",table.name);
|
|
for( f in fields ) {
|
|
var t = table.hfields.get(f);
|
|
if( t == null )
|
|
continue;
|
|
var t = switch( t ) {
|
|
case DId, DUId: DInt;
|
|
case DBigId: DFloat;
|
|
case DText,DSmallText: DTinyText;
|
|
default: t;
|
|
};
|
|
inputField(table,{ name : f, type : t },null,false,if( params == null ) null else params.get(f));
|
|
}
|
|
style.addSubmit("Search");
|
|
style.endForm();
|
|
|
|
style.beginTable("results");
|
|
style.beginLine(true,"header");
|
|
style.text("actions");
|
|
|
|
if( sinfos != null && sinfos.names != null ) {
|
|
for( f in sinfos.names ) {
|
|
style.nextRow(true);
|
|
if( table.hfields.exists(f) ) {
|
|
var cur = (order == f);
|
|
var curNeg = (order == "-"+f);
|
|
style.link(table.className+"/search?"+paramsStr+"__o="+(cur ? "-"+f : f),(cur ? "+" : curNeg ? "-" : "") + f);
|
|
} else
|
|
style.text(f);
|
|
}
|
|
} else {
|
|
for( f in table.fields ) {
|
|
if( has(rights.invisible,f.name) )
|
|
continue;
|
|
style.nextRow(true);
|
|
var cur = (order == f.name);
|
|
var curNeg = (order == "-"+f.name);
|
|
style.link(table.className+"/search?"+paramsStr+"__o="+(cur ? "-"+f.name : f.name),(cur ? "+" : curNeg ? "-" : "") + f.name);
|
|
}
|
|
}
|
|
style.endLine();
|
|
|
|
var odd = false;
|
|
for( r in results ) {
|
|
var k = table.fields.iterator();
|
|
style.beginLine(if( odd ) "odd" else null);
|
|
style.link(table.className+"/edit/"+table.identifier(r),"Edit");
|
|
odd = !odd;
|
|
if( sinfos != null && sinfos.names != null ) {
|
|
var rinfos = getSInfos(r);
|
|
for( v in rinfos.values ) {
|
|
style.nextRow(false);
|
|
style.text(Std.string(v));
|
|
}
|
|
} else {
|
|
var rinst = getRights(r);
|
|
for( f in k ) {
|
|
if( has(rights.invisible,f.name) )
|
|
continue;
|
|
var data = Reflect.field(r,f.name);
|
|
var str = try Std.string(data) catch( e : Dynamic ) { if(!Std.is(data,Date)) Lib.rethrow(e); "#INVALID"; };
|
|
if( str.length >= 20 )
|
|
str = str.substr(0,17) + "...";
|
|
style.nextRow(false);
|
|
if( has(rinst.invisible,f.name) )
|
|
style.text("???");
|
|
else if( data == null )
|
|
style.text(str)
|
|
else switch( f.type ) {
|
|
case DEncoded:
|
|
style.text(Id.decode(data),str);
|
|
case DDate:
|
|
style.text(str.substr(0,10)); // remove 00:00:00 time
|
|
case DFlags(flags,_):
|
|
var fl = [];
|
|
for( i in 0...flags.length )
|
|
if( data & (1 << i) != 0 )
|
|
fl.push(flags[i]);
|
|
str = fl.join(",");
|
|
if( str.length >= 20 )
|
|
style.text(str.substr(0,17) + "...",fl.join(",")+" ("+data+")");
|
|
else
|
|
style.text(str,"("+data+")");
|
|
default:
|
|
style.text(str);
|
|
}
|
|
}
|
|
}
|
|
style.endLine();
|
|
}
|
|
style.endTable();
|
|
|
|
if( order != null )
|
|
paramsStr += "__o="+order+";";
|
|
|
|
if( page > 0 )
|
|
style.link(table.className+"/search?"+paramsStr+"__p="+(page-1),"Previous");
|
|
else
|
|
style.text("Previous");
|
|
style.text(" | ");
|
|
if( hasNext )
|
|
style.link(table.className+"/search?"+paramsStr+"__p="+(page+1),"Next");
|
|
else
|
|
style.text("Next");
|
|
style.end();
|
|
}
|
|
|
|
function syncAction( t : TableInfos, act : Array<String>, text : String, ?def ) {
|
|
if( !hasSyncAction ) {
|
|
style.beginList();
|
|
hasSyncAction = true;
|
|
}
|
|
style.beginItem();
|
|
style.checkBox(t.className+"@"+act.join("@"),if( def == null ) true else def);
|
|
style.text(text);
|
|
style.endItem();
|
|
}
|
|
|
|
function doSync( params : Map<String,String> ) {
|
|
var order = ["create","add","reldel","idxdel","update","remove","rename","idxadd","reladd"];
|
|
var cmd = new Array();
|
|
for( p in params.keys() ) {
|
|
if( !~/[A-Za-z0-9_@]*/.match(p) )
|
|
throw "Invalid command "+p;
|
|
cmd.push(p.split("@"));
|
|
}
|
|
cmd.sort(function(c1,c2) {
|
|
var p1 = 0, p2 = 0;
|
|
for( i in 0...order.length )
|
|
if( order[i] == c1[1] )
|
|
p1 = i;
|
|
else if( order[i] == c2[1] )
|
|
p2 = i;
|
|
return p1 - p2;
|
|
});
|
|
for( data in cmd ) {
|
|
var tname = data.shift();
|
|
#if php
|
|
// php replaces dots by _ in post keys
|
|
tname = tname.split("_").join(".");
|
|
#end
|
|
var table = new TableInfos(tname);
|
|
var act = data.shift();
|
|
var field = data.shift();
|
|
try {
|
|
switch( act ) {
|
|
case "create":
|
|
execute(table.createRequest(false));
|
|
case "add":
|
|
execute(table.addFieldRequest(field));
|
|
case "update":
|
|
execute(table.updateFieldRequest(field));
|
|
case "remove":
|
|
execute(table.removeFieldRequest(field));
|
|
case "rename":
|
|
execute(table.renameFieldRequest(field,data.shift()));
|
|
case "reladd":
|
|
execute(table.addRelationRequest(field,data.shift()));
|
|
case "reldel":
|
|
execute(table.deleteRelationRequest(field));
|
|
case "idxadd":
|
|
execute(table.addIndexRequest(data,field == "true"));
|
|
case "idxdel":
|
|
execute(table.deleteIndexRequest(field));
|
|
default:
|
|
throw "Unknown action "+act;
|
|
}
|
|
} catch( e : Dynamic ) {
|
|
index(Std.string(e));
|
|
return;
|
|
}
|
|
}
|
|
style.goto("");
|
|
}
|
|
|
|
function indexId( i : { unique : Bool, keys : List<String> } ) {
|
|
return i.unique+"@"+i.keys.join("@");
|
|
}
|
|
|
|
/**
|
|
* check for differences between classes and tables in DB
|
|
*/
|
|
function needSync( t : TableInfos ) {
|
|
var desc = execute(t.descriptionRequest()).getResult(1);
|
|
var inf = TableInfos.fromDescription(desc);
|
|
var renames = new Map();
|
|
hasSyncAction = false;
|
|
|
|
// ADD/CHANGE FIELDS
|
|
for( f in t.fields ) {
|
|
var t2 = inf.fields.get(f.name);
|
|
if( t2 == null ) {
|
|
var rename = false;
|
|
for( n in inf.fields.keys() )
|
|
if( !t.hfields.exists(n) && Type.enumEq(inf.fields.get(n),f.type) && inf.nulls.get(n) == t.nulls.get(f.name) ) {
|
|
rename = true;
|
|
renames.set(n,true);
|
|
syncAction(t,["rename",n,f.name],'Rename field "$n" to "${f.name}"');
|
|
break;
|
|
}
|
|
syncAction(t,["add",f.name],"Add field "+f.name,!rename);
|
|
} else {
|
|
inf.fields.remove(f.name);
|
|
var isnull = inf.nulls.get(f.name);
|
|
var changed = false;
|
|
var txt = "Change "+f.name+" : ";
|
|
if( !Type.enumEq(f.type,t2) && !TableInfos.sameDBStorage(t2,f.type) ) {
|
|
changed = true;
|
|
txt += " S"+Std.string(t2).substr(1)+" becomes S"+Std.string(f.type).substr(1);
|
|
}
|
|
if( isnull != t.nulls.get(f.name) ) {
|
|
if( changed )
|
|
txt += " and";
|
|
else
|
|
changed = true;
|
|
if( isnull )
|
|
txt += " can't be NULL";
|
|
else
|
|
txt += " can be NULL";
|
|
}
|
|
if( changed )
|
|
syncAction(t,["update",f.name],txt);
|
|
}
|
|
}
|
|
// REMOVE FIELDS
|
|
for( f in inf.fields.keys() )
|
|
syncAction(t,["remove",f],"Remove field "+f,!renames.exists(f));
|
|
// ADD RELATIONS
|
|
for( r in t.relations ) {
|
|
if( !t.isRelationActive(r) )
|
|
continue;
|
|
var tname = TableInfos.unescape(r.manager.table_name);
|
|
var found = false;
|
|
var setnull = t.nulls.get(r.key);
|
|
if( setnull && untyped r.cascade == true )
|
|
setnull = null;
|
|
for( r2 in inf.relations )
|
|
if( (t.name + "_" + r.prop == r2.name || TableInfos.OLD_COMPAT) &&
|
|
r.key == r2.key &&
|
|
tname.toLowerCase() == r2.table.toLowerCase() &&
|
|
r.manager.table_keys.length == 1 && r.manager.table_keys[0] == r2.id &&
|
|
r2.setnull == setnull
|
|
) {
|
|
found = true;
|
|
inf.relations.remove(r2);
|
|
break;
|
|
}
|
|
if( !found )
|
|
syncAction(t,["reladd",r.key,r.prop],"Add Relation "+r.prop+"("+r.key+") on "+tname+"("+r.manager.table_keys[0]+")"+if( setnull ) " set-null" else "");
|
|
}
|
|
// REMOVE RELATIONS
|
|
for( r in inf.relations )
|
|
syncAction(t,["reldel",r.name],"Remove Relation "+r.name+"("+r.key+") on "+r.table+"("+r.id+")"+if( r.setnull ) " set-null" else "");
|
|
// INDEXES
|
|
var hidx = new Map();
|
|
for( i in t.indexes )
|
|
hidx.set(indexId(i),i);
|
|
var used = new List();
|
|
for( r in t.relations ) {
|
|
var found : { keys : List<String>, unique : Bool } = null;
|
|
for( i in t.indexes )
|
|
if( i.keys.first() == r.key && (found == null || found.keys.length < i.keys.length) )
|
|
found = i;
|
|
if( found == null ) {
|
|
// in primary key ?
|
|
if( t.primary.first() == r.key )
|
|
continue;
|
|
// default relation-index
|
|
found = { keys : Lambda.list([r.key]), unique : false };
|
|
}
|
|
hidx.remove(indexId(found));
|
|
for( i in inf.indexes )
|
|
if( i.keys.join("#") == found.keys.join("#") && i.unique == found.unique ) {
|
|
used.add(i);
|
|
found = null;
|
|
break;
|
|
}
|
|
// we need it
|
|
if( found != null )
|
|
hidx.set(indexId(found),found);
|
|
}
|
|
for( i in used )
|
|
inf.indexes.remove(i.name);
|
|
for( iname in inf.indexes.keys() ) {
|
|
var i = inf.indexes.get(iname);
|
|
if( !hidx.remove(indexId(i)) )
|
|
syncAction(t,["idxdel",iname],"Remove "+(if( i.unique ) "Unique " else "")+"Index "+iname+" ("+i.keys.join(",")+")");
|
|
}
|
|
for( i in hidx )
|
|
syncAction(t,["idxadd",indexId(i)],"Add "+(if( i.unique ) "Unique " else "")+"Index ("+i.keys.join(",")+")");
|
|
// PRIMARY KEYS
|
|
if( (inf.primary == null) != (t.primary == null) || (inf.primary != null && inf.primary.join("-") != t.primary.join("-")) ) {
|
|
style.text("PRIMARY KEY CHANGED !");
|
|
hasSyncAction = true;
|
|
}
|
|
if( hasSyncAction )
|
|
style.endList();
|
|
return hasSyncAction;
|
|
}
|
|
|
|
public function process( ?url : Array<String> ) {
|
|
if( url == null ) {
|
|
url = Web.getURI().split("/");
|
|
url.shift(); // empty : url starts with /
|
|
url.shift(); // "admin"
|
|
if( url[0] == "index.n" )
|
|
url.shift();
|
|
}
|
|
if( url.length == 0 ) url.push("");
|
|
var params = Web.getParams();
|
|
switch( url[0] ) {
|
|
case "":
|
|
|
|
style = new AdminStyle(null);
|
|
index();
|
|
return;
|
|
|
|
case "doSync":
|
|
style = new AdminStyle(null);
|
|
doSync(params);
|
|
return;
|
|
}
|
|
var table = new TableInfos(url.shift());
|
|
style = new AdminStyle(table);
|
|
var act = url.shift();
|
|
switch( act ) {
|
|
case "insert":
|
|
insert(table,params);
|
|
case "doInsert":
|
|
doInsert(table,params);
|
|
case "edit":
|
|
edit(table,url.join("/"));
|
|
case "doEdit":
|
|
doEdit(table,url.join("/"),params);
|
|
case "doCreate":
|
|
doCreate(table);
|
|
case "doDrop":
|
|
doDrop(table);
|
|
case "doCleanup":
|
|
doCleanup(table);
|
|
case "doDelete":
|
|
doDelete(table,url.join("/"));
|
|
case "doDownload":
|
|
doDownload(table,url.shift(),url.shift());
|
|
case "search":
|
|
search(table,params);
|
|
default:
|
|
throw "Unknown action "+act;
|
|
}
|
|
}
|
|
|
|
static function log(msg:String) {
|
|
#if neko
|
|
Web.logMessage("[DBADM] " + neko.Web.getHostName() + " " + Date.now().toString() + " " + neko.Web.getClientIP() + " - " + msg);
|
|
#end
|
|
}
|
|
|
|
public static function handler() {
|
|
Manager.initialize(); // make sure it's been done
|
|
try {
|
|
new Admin().process();
|
|
} catch( e : Dynamic ) {
|
|
// rollback in case of multiple delete/update - no effect on DB struct changes
|
|
// since they are done outside of transaction
|
|
Manager.cnx.rollback();
|
|
|
|
Sys.print("<h2>Error :</h2>"+Std.string(e));
|
|
Sys.print("<h2>Stack :</h2><pre>");
|
|
Sys.print(haxe.CallStack.toString(haxe.CallStack.exceptionStack()));
|
|
Sys.print("</pre>");
|
|
}
|
|
}
|
|
|
|
public static function initializeDatabase( initIndexes = true, initRelations = true ) {
|
|
var a = new Admin();
|
|
var tables = a.getTables();
|
|
for( t in tables )
|
|
a.execute(t.createRequest(false));
|
|
for( t in tables ) {
|
|
if( initIndexes )
|
|
for( i in t.indexes )
|
|
a.execute(t.addIndexRequest(Lambda.array(i.keys), i.unique));
|
|
if( initRelations )
|
|
for( r in t.relations )
|
|
a.execute(t.addRelationRequest(r.key, r.prop));
|
|
}
|
|
}
|
|
|
|
}
|