<Foreign method="getTickets" via="ticket:orderid=orderid"/>
<Foreign method="getVouchers" via="voucher:orderid=orderid"/>
</Table>
- <Table name="ticket" backup="yes">
+ <Table name="ticket" backup="yes" base="BarcodeTable">
<!--a 8-32 char code (code39: case-insensitive letters+digits) for the ticket-->
<Column name="ticketid" type="string:32" primarykey="yes"/>
<Column name="eventid" type="int32" foreignkey="event:eventid"/>
<Column name="status" type="int32" notnull="yes"/>
<Column name="orderid" type="int32" foreignkey="order:orderid" notnull="yes"/>
</Table>
- <Table name="voucher" backup="yes">
+ <Table name="voucher" backup="yes" base="BarcodeTable">
<!--a 8-32 char code (code39: case-insensitive letters+digits) for the voucher-->
<Column name="voucherid" type="string:32" primarykey="yes"/>
<!--price of the voucher (0 if cancelled)-->
return;
}
tf.write(PHPSTART);
- QString code="class WT"+tbl.name()+" extends WobTable\n{\n";
+ QString code="class WT"+tbl.name()+" extends "+tbl.baseClass()+"\n{\n";
//initializer
code+="protected function __construct(array $data,$isfromdb){parent::__construct($data,$isfromdb,\""+tbl.name()+"\");}\n\n";
//static get instance
+ QStringList cols=tbl.columns();
QStringList pcols=tbl.primaryColumns();
code+="public static function getFromDB(";
for(int i=0;i<pcols.size();i++){
code+="public static function selectFromDB($where){\n\tglobal "+dbi+";\n\t$res="+dbi+"->select(\""+tbl.name()+"\",\"*\",$where);\n\tif($res===false || count($res)<1)return array();\n\t";
code+="$r=array();\n\tforeach($res as $row)\n\t\t$r[]=new WT"+tbl.name()+"($row,true);\n\treturn $r;\n}\n\n";
- //TODO:automatic resolution of internal foreign keys
+ //go through columns, generate specific code
+ for(int i=0;i<cols.size();i++){
+ //automatic resolution of internal foreign keys
+ if(tbl.columnIsForeign(cols[i])){
+ code+="public function get"+cols[i]+"(){\n\tglobal "+dbi+";\n\treturn WT";
+ QStringList foreign=tbl.columnForeign(cols[i]).split(":");
+ code+=foreign[0]+"::selectFromDB(\""+foreign[1]+"=\"."+dbi+"->escapeColumn(\""+foreign[0]+"\",\""+foreign[1]+"\",$this->"+cols[i]+"));\n}\n\n";
+ }
+ //implement enum check for set method of enum columns
+ if(tbl.columnType(cols[i]).startsWith("enum")){
+ code+="private function verifyValue"+cols[i]+"($v){if(false";
+ QList<QPair<QString,int> >ens=tbl.columnEnums(cols[i]);
+ QList<int>envs;
+ for(int j=0;j<ens.size();j++){
+ int v=ens[j].second;
+ if(envs.contains(v))continue;
+ envs.append(v);
+ code+="||$v=="+QString::number(v);
+ }
+ code+=")return true;else return false;}\n\n";
+ }
+ }
//reverse resolution of configured foreign keys
QStringList fs=tbl.foreigns();
qDebug("Warning: Foreign clause %s of table %s has illegal syntax. Should be foreigntable:column=localcolumn.",fs[i].toAscii().data(),tbl.name().toAscii().data());
continue;
}
- code+="public function "+fs[i]+"(){\n\treturn WT"+foreign[0]+"::selectFromDB(\""+foreign[1]+"=\"."+dbi+"->escapeColumn(\"";
+ code+="public function "+fs[i]+"(){\n\tglobal "+dbi+";\n\treturn WT"+foreign[0]+"::selectFromDB(\""+foreign[1]+"=\"."+dbi+"->escapeColumn(\"";
code+=foreign[0]+"\",\""+foreign[1]+"\",$this->"+local+"));\n}\n\n";
}
//extend schema file
//column definitions
- QStringList cols=tbl.columns();
code="\t$this->scheme[\""+tbl.name()+"\"]=array(";
for(int i=0;i<cols.size();i++){
if(i)code+=",";
return;
}
m_backup=str2bool(tbl.attribute("backup","0"));
+ m_base=tbl.attribute("base","WocTable");
qDebug("Info: parsing table %s",m_name.toAscii().data());
QDomNodeList nl=tbl.elementsByTagName("Column");
//Columns
return false;
}
+QList<QPair<QString,int> > WocTable::columnEnums(QString c)const
+{
+ for(int i=0;i<m_columns.size();i++)
+ if(m_columns[i].name==c)
+ return m_columns[i].enumvals;
+ return QList<QPair<QString,int> >();
+}
QList<QPair<QString,int> > WocTable::getEnums()const
{
QString name()const{return m_name;}
bool inBackup()const{return m_backup;}
+ QString baseClass()const{return m_base;}
bool hasColumn(QString)const;
QStringList columns()const;
QString columnForeign(QString)const;
bool columnIsIndexed(QString)const;
bool columnIsUnique(QString)const;
+ QList<QPair<QString,int> > columnEnums(QString)const;
QList<QPair<QString,int> > getEnums()const;
QList<QMap<QString,QString> > presets()const{return m_presets;}
private:
bool m_valid,m_backup;
- QString m_name;
+ QString m_name,m_base;
struct s_col {
QString name,type,foreign,defaultval;
bool isnull,isprime,isindex,isunique;
}
/**returns whether the table exists; must be implemented by driver*/
- public abstract function haveTable($tablename);
+ public abstract function hasTable($tablename);
/**begins a transaction; must be implemented by driver; use sqlBeginTransaction to create the SQL statement!*/
public abstract function beginTransaction();
/**unlocks the database - only used by the backup functions; if necessary should also commit a transaction; default just commits*/
protected function unlockDB(){$this->commitTransaction();}
- /**gets some data from the database; $table is the name of the table, $cols is the list of columns to return or "*" for all, $where is the where clause of the SQL-statement, $orderby may contain additional ORDER BY or GROUP BY clauses; returns array of rows, which are in *_fetch_array format; returns false on error; use sqlSelect to create the SQL statement!*/
+ /**gets some data from the database; $table is the name of the table, $cols is the list of columns to return or "*" for all, $where is the where clause of the SQL-statement, $orderby may contain additional ORDER BY or GROUP BY clauses; returns array of rows, which are in *_fetch_array format; returns false on error; use sqlSelect to create the SQL statement!; make sure that NULL values are returned as PHP value null (most DB drivers already do this)*/
public abstract function select($table,$cols,$where,$orderby="");
/**insert values into a table; returns false on failure, the new primary key if a sequence was set, true otherwise; use sqlInsert to create the SQL statement!*/
return "ROLLBACK TRANSACTION";
}
- /**escapes integers; the default implementation just makes sure it is an int*/
+ /**escapes integers; the default implementation just makes sure it is an int (false and null are translated to NULL)*/
public function escapeInt($i)
{
- if($i === false)return "NULL";
+ if($i === false || $i === null)return "NULL";
return round($i + 0);
}
- /**escapes strings; the default uses addslashes and encloses the value in ''; it is recommended to overwrite this with the proper escaping procedure for the target DB*/
+ /**escapes strings; the default uses addslashes and encloses the value in ''; it is recommended to overwrite this with the proper escaping procedure for the target DB (false and null are translated to NULL)*/
public function escapeString($s)
{
- if($s === false) return "NULL";
+ if($s === false || $s === null) return "NULL";
return "'".addslashes($s)."'";
}
/**escapes blobs; the default uses addslashes and encloses the value in ''; it is recommended to overwrite this with the proper escaping procedure for the target DB*/
public function escapeBlob($s)
{
- if($s === false) return "NULL";
+ if($s === false || $s===null) return "NULL";
return "'".addslashes($s)."'";
}
- /**escapes a boolean value; the default translates 0, '0', 'f', 'false', 'no' to FALSE and numerics !=0, 't', 'true', 'yes' to TRUE; any other value (incl. the PHP constant false) is translated to NULL (exception: the constant true is treated as 'true')*/
+ /**escapes a boolean value; the default translates 0, '0', 'f', 'false', 'n', 'no', false to FALSE and numerics !=0, 't', 'true', 'y', 'yes', true to TRUE; any other value (incl. the PHP constant null) is translated to NULL*/
public function escapeBool($s)
{
+ if($s===null)return "NULL";
if(is_numeric($s)){
if(($s+0)!=0)return "TRUE";
else return "FALSE";
}
- if($s===false)return "NULL";
+ if($s===false)return "FALSE";
if($s===true)return "TRUE";
switch(strtolower($s)){
case "t":case "true":case "yes":case "y":
/**tries to find out whether the connected DB version is usable*/
public function canUseDb()
{
- if(!$this->haveTable("config"))
+ if(!$this->hasTable("config"))
return false;
return $this->getConfig("MagicSmokeVersion")==$this->needVersion();
}
/**returns the error string of the last operation*/
public abstract function lastError();
- /**returns whether the result value is NULL; the default interprets both null and false as NULL; overwrite if the DB can return false for a boolean*/
+ /**returns whether the result value is NULL; the default interprets only the special value null as NULL*/
public function isNull($val)
{
- if($val===false || $val===null)return true;
+ if($val===null)return true;
else return false;
}
private function unescapeBackup($fmt,$val)
{
switch($fmt){
- case "NULL":return false;
+ case "NULL":return null;
case "int":return $val+0;
case "str":return base64_decode($val);
case "bool":return $val+0;
default:
print("Warning: encountered unknown data encoding \"".htmlspecialchars($fmt)."\". Using NULL instead.<br>\n");
- return false;
+ return null;
}
}
case "table":
$data=array();
$table=$cmd[1];
- if(!$this->haveTable($table)){
+ if(!$this->hasTable($table)){
print("Switching to non-existing table ".htmlspecialchars($table)." - ignoring data that follows.<br>\n");
$table="";
}else
die("Cannot make this database transaction safe, aborting");
}
- public function haveTable($tnm)
+ public function hasTable($tnm)
{
global $dbScheme;
- if(!$dbScheme->haveTable($tnm))return false;
+ if(!$dbScheme->hasTable($tnm))return false;
$res=mysqli_query($this->dbhdl,"select * from ".$this->tableName($tnm)." where 1=2");
if($res===false)return false;
mysqli_free_result($res);
/**escapes strings; it uses mysqli_escape_string and encloses the value in ''*/
public function escapeString($s)
{
- if($s === false) return "NULL";
+ if($s === false||$s===null) return "NULL";
return "'".mysqli_real_escape_string($this->dbhdl,$s)."'";
}
/**escapes blobs; it uses mysqli_escape_string and encloses the value in '' - blobs are binary strings in MySQL*/
public function escapeBlob($s)
{
- if($s === false) return "NULL";
+ if($s === false||$s===null) return "NULL";
return "'".mysqli_real_escape_string($this->dbhdl,$s)."'";
}
-};
\ No newline at end of file
+};
+
+?>
\ No newline at end of file
}
/**returns whether a table exists in the schema*/
- public function haveTable($t)
+ public function hasTable($t)
{
return in_array($t,array_keys($this->scheme));
}
return $r;
}
+ /**return whether the table has this column*/
+ public function tableHasColumn($tab,$col)
+ {
+ return isset($this->scheme[$tab][$col]);
+ }
+
/**return default lines of the table for the initialization; returns empty array if there are none*/
public function tableDefaults($tab)
{
//
//
+class ValueOutOfRange extends Exception
+{
+ public function __construct($tab,$col,$val)
+ {
+ $this->message="Value of table '$tab' column '$col' is out of range. Value '$val' not allowed.";
+ }
+};
+
class WobTable
{
private $data;
public function __set($name,$value)
{
+ global $db,$dbScheme;
//verify name against scheme
- //escape value
+ if(!$dbScheme->tableHasColumn($this->table,$name))return;
+ //verify enum vals
+ $vm="verifyValue".$name;
+ if(method_exists($this,$vm))
+ if(!$t->$vm($value))
+ throw ValueOutOfRange($this->table,$name,$value);
//DB update (if from DB)
+ $succ=true;
+ if($this->isfromdb){
+ $succ=$db->update($this->table,array($name => $value),$this->where());
+ }
//if successful: store
+ if($succ)$this->data[$name]=$value;
+ }
+
+ public function where()
+ {
+ global $dbScheme,$db;
+ $r="";
+ $pk=$dbScheme->primaryKeyColumns($this->table);
+ foreach($pk as $c){
+ if($r!="")$r.=" AND ";
+ $r.=$c."=".$db->escapeColumn($this->table,$c,$this->data[$c]);
+ }
+ return $r;
}
public function __get($name)
{
//verify name
- //return value or false
+ //return value or null
+ if(isset($this->data[$name]))return $this->data[$name];
+ else return null;
}
public function __isset($name)
{
+ global $db;
//verify name and return true on existence AND notnull
+ if(isset($this->data[$name]))return !$db->isNull($this->data[$name]);
+ else return false;
}
public function __unset($name)
{
+ global $dbScheme;
//reset to null
+ if($dbScheme->tableHasColumn($name))$this->data[$name]=null;
}
- /**insert the object under a new primary key value into the DB (implicitly calls newKey)*/
+ /**insert the object under a new primary key value into the DB (implicitly calls newKey); returns true on success*/
public function insert()
{
+ //remove PK from object (so it is set to default)
+ global $dbScheme;
+ $this->isfromdb=false;
+ $pk=$dbScheme->primaryKeyColumns($this->table);
+ foreach($pk as $c)unset($this->data[$c]);
+ //optionally create new key
+ $this->newKey();
+ //now insert
+ $r=$db->insert($this->table,$this->data);
+ if($r===false)return false;
+ //TODO: make this a bit more safe:
+ if($r!==true)$this->data[$pk[0]]=$r;
+ return true;
}
/**generate a new primary key value for insert and marks the object as not yet in the DB; the default sets the primary key to NULL; call the original first if you overwrite it*/
public function newKey()
{
- $this->isfromdb=false;
- //get primary key columns and set them to false
}
};