|
|
#pike __REAL_VERSION__ |
#define TABLE_ERR(msg) error("(Table) "+msg+"\n") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class table { |
protected mapping fieldmap; |
protected array table, fields, types; |
|
protected array|int remap(array|string|int cs, int|void forgive) |
{ |
array v = ({}); |
int ap = arrayp(cs); |
if(!ap) cs = ({ cs }); |
foreach(cs, string|int f) |
if(undefinedp(intp(f)?f:fieldmap[lower_case(f)])) { |
if(!forgive) |
TABLE_ERR("Unknown field '"+f+"'"); |
} else |
v += ({ intp(f)?f:fieldmap[lower_case(f)] }); |
return ap?v:v[0]; |
} |
|
this_program copy(array|void tab, array|void fie, array|void typ) |
{ |
return this_program(tab||table,fie||fields,typ||types); |
} |
|
|
|
string encode() |
{ |
return encode_value(([ "table":table,"fields":fields,"types":types ])); |
} |
|
|
|
object decode(string s) |
{ |
mapping m = decode_value(s); |
return copy(m->table, m->fields, m->types); |
} |
|
protected mixed cast(string type) |
{ |
switch(type) { |
case "array": |
return copy_value(table); |
case "string": |
return ASCII->encode(this); |
} |
return UNDEFINED; |
} |
|
|
|
protected array(string) _indices() |
{ |
return copy_value(fields); |
} |
|
|
|
protected array(array) _values() |
{ |
return copy_value(table); |
} |
|
|
protected int _sizeof() |
{ |
return sizeof(table); |
} |
|
|
|
protected this_program reverse() |
{ |
return copy(predef::reverse(table), fields, types); |
} |
|
|
array col(int|string column) |
{ |
return copy_value(predef::column(table, remap(column))); |
} |
|
|
array row(int row_number) |
{ |
return copy_value(table[row_number]); |
} |
|
|
protected array `[](int|string column) |
{ |
return col(column); |
} |
|
|
|
|
protected int(0..1) `==(object table) |
{ |
return (equal(Array.map(fields, lower_case), |
Array.map(indices(table), lower_case)) && |
equal(this::table, values(table))); |
} |
|
|
|
|
this_program append_bottom(object table) |
{ |
if(!equal(Array.map(indices(table), lower_case), |
Array.map(fields, lower_case))) |
TABLE_ERR("Table fields are not equal."); |
return copy(this::table+values(table), fields, types); |
} |
|
|
|
|
this_program append_right(object table) |
{ |
if(sizeof(table) != sizeof(this::table)) |
TABLE_ERR("Table sizes are not equal."); |
array v = values(table); |
for(int r = 0; r < sizeof(this::table); r++) |
v[r] = this::table[r] + v[r]; |
return copy(v, fields+indices(table), types+table->all_types()); |
} |
|
protected mixed op_col(function f, int|string c, mixed ... args) |
{ |
c = remap(c); |
mixed x = table[0][c]; |
for(int r = 1; r < sizeof(table); r++) |
f(x, table[r][c], @args); |
return x; |
} |
|
mixed sum_col(int|string c) |
{ |
return `+(@column(table, remap(c))); |
} |
|
mixed average_col(int|string c) |
{ |
return sum_col(c)/sizeof(table); |
} |
|
mixed min_col(int|string c) |
{ |
return op_col(min, c); |
} |
|
mixed max_col(int|string c) |
{ |
return op_col(max, c); |
} |
|
|
this_program select(int|string ... columns) |
{ |
array t = ({}); |
columns = remap(columns); |
for(int r = 0; r < sizeof(table); r++) |
t += ({ rows(table[r], columns) }); |
return copy(t, rows(fields, columns), rows(types, columns)); |
} |
|
|
|
this_program remove(int|string ... columns) |
{ |
return select(@remap(fields) - remap(columns, 1)); |
} |
|
|
|
|
|
this_program where(array(int|string)|int|string columns, function f, |
mixed ... args) |
{ |
array t = ({}); |
f = f || lambda(mixed x) { return x; }; |
columns = remap(arrayp(columns)?columns:({ columns })); |
foreach(table, mixed row) |
if(f(@rows(row, columns), @args)) |
t += ({ row }); |
return copy(t, fields, types); |
} |
|
|
|
|
this_program group(mapping(int|string:function)|function f, mixed ... args) |
{ |
if(!sizeof(table)) |
return this; |
|
if(functionp(f)) { |
if(!arrayp(args[0])) |
args[0] = ({ args[0] }); |
f = mkmapping(args[0], allocate(sizeof(args[0]), f)); |
args = args[1..]; |
} |
|
mapping m = ([]); |
array cs = remap(indices(f)); |
f = mkmapping(cs, values(f)); |
array(int) keys = indices(fields) - cs; |
foreach(table, array row) { |
string key = encode_value(rows(row, keys)); |
if(array a = m[key]) |
foreach(cs, int c) |
a[c] = f[c](a[c], row[c], @args); |
else |
m[key] = copy_value(row); |
} |
return copy(values(m), fields, types); |
} |
|
|
|
this_program sum(int|string ... columns) |
{ |
return group(`+, columns); |
} |
|
|
|
|
this_program distinct(int|string ... columns) |
{ |
if(!sizeof(columns)) |
return sum(); |
array f = remap(fields) - remap(columns); |
mapping m = mkmapping(f, Array.map(f, lambda(mixed unused) |
{ return lambda(mixed x1, |
mixed x2) |
{ return x1; }; })); |
return group(m); |
} |
|
|
|
|
|
|
object map(function f, array(int|string)|int|string columns, mixed ... args) |
{ |
int ap = arrayp(columns); |
array t = copy_value(table); |
if(!catch(columns = remap(ap?columns:({ columns })))) { |
for(int r = 0; r < sizeof(t); r++) { |
mixed v = f(@rows(t[r], columns), @args); |
if(arrayp(v)) |
for(int i = 0; i < sizeof(v); i++) |
t[r][columns[i]] = v[i]; |
else |
t[r][columns[0]] = v; |
} |
} |
return copy(t, fields, types); |
} |
|
protected this_program _sort(int is_reversed, int|string ... cs) |
{ |
if(!sizeof(cs)) |
return this; |
int c; |
array t = copy_value(table); |
if(!catch(c = remap(cs[-1]))) |
{ |
mapping m = ([]); |
for(int r = 0; r < sizeof(t); r++) |
{ |
mixed d; |
if(!m[d = t[r][c]]) |
m[d] = ({ t[r] }); |
else |
m[d] += ({ t[r] }); |
} |
array i = indices(m), v = values(m); |
if(types[c] && types[c]->type=="num") |
i = (array(float))i; |
predef::sort(i, v); |
t = (is_reversed ? predef::reverse(v) : v)*({}); |
} |
return is_reversed ? |
copy(t, fields, types)->rsort(@cs[0..(sizeof(cs)-2)]) : |
copy(t, fields, types)->sort(@cs[0..(sizeof(cs)-2)]); |
} |
|
|
|
|
|
|
|
|
this_program sort(int|string ... columns) |
{ |
return _sort(0, @columns); |
} |
|
|
object rsort(int|string ... columns) |
{ |
return _sort(1, @columns); |
} |
|
|
|
this_program limit(int n) |
{ |
return copy(table[0..(n-1)], fields, types); |
} |
|
|
|
|
this_program rename(string|int from, string to) |
{ |
array a = copy_value(fields); |
a[remap(from)] = to; |
return copy(table, a, types); |
} |
|
|
|
|
|
|
mapping type(int|string column, void|mapping type) |
{ |
if(query_num_arg() == 2) |
types[remap(column)] = copy_value(type); |
return copy_value(types[remap(column)]); |
} |
|
array all_types() |
{ |
return copy_value(types); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected void create(array(array) table, array(string) column_names, |
array(mapping(string:string))|void column_types) |
{ |
if(!arrayp(table)) |
TABLE_ERR("Table not array"); |
if(!arrayp(column_names)) |
TABLE_ERR("Fields not array"); |
if(sizeof(table) && sizeof(table[0]) != sizeof(column_names)) |
TABLE_ERR("Table and field sizes differ"); |
foreach(column_names, string s) |
if(!stringp(s)) |
TABLE_ERR("Field name not string"); |
|
this::table = copy_value(table); |
fields = copy_value(column_names); |
types = allocate(sizeof(column_names)); |
|
if(column_types) |
for(int i = 0; i < sizeof(fields); i++) |
if(!column_types[i] || mappingp(column_types[i])) |
types[i] = copy_value(column_types[i]); |
else |
TABLE_ERR("Field type not mapping"); |
|
array(int) a = indices(allocate(sizeof(fields))); |
fieldmap = mkmapping(Array.map(fields, lower_case), a); |
} |
} |
|
object Separated = class { |
protected string _string(mixed x) { return (string)x; } |
|
object decode(string s, void|mapping options) |
{ |
string rowsep = options->rowsep||"\n"; |
string colsep = options->colsep||"\t"; |
array t = Array.map(s/rowsep, `/, colsep); |
return table(t[1..], t[0], options->types); |
} |
|
mixed encode(object t, void|mapping options) |
{ |
options = options || ([]); |
string rowsep = options->rowsep||"\n"; |
string colsep = options->colsep||"\t"; |
return Array.map(({ indices(t) }) + values(t), |
lambda(array r, string colsep) |
{ return Array.map(r, _string)*colsep; }, colsep)*rowsep; |
} |
}(); |
|
|
|
|
object ASCII = class { |
|
object decode(string s, void|mapping options) |
{ |
|
return 0; |
} |
|
|
|
|
|
|
|
|
string encode(object t, void|mapping options) |
{ |
options = options || ([]); |
mapping sizes = ([]); |
array fields = indices(t); |
string indent = " " * options->indent; |
|
t = t->copy(({ fields }) + values(t)); |
for(int field = 0; field < sizeof(fields); field++) |
t = (t->map(lambda(mixed m, int field, mapping sizes) |
{ |
m = (string)m; |
sizes[field] = max(sizeof(m), sizes[field]); |
return m; |
}, |
field, field, sizes)-> |
map(lambda(string s, string size, int num) |
{ |
return sprintf("%"+(num?"":"-")+size+"s", s); |
}, |
field, (string)sizes[field], |
(t->type(field)||([]))->type == "num")); |
|
string l = (indent+"-"+ |
Array.map(values(sizes), |
lambda(int n) |
{ return "-" * n; })*"---"+"-"); |
array table = values(t); |
return (indent+" "+table[0]*" "+"\n"+l+"\n"+ |
Array.map(table[1..], lambda(array row, string indent) |
{ return indent+" "+row*" "; }, |
indent)*"\n"+(sizeof(table)>1?"\n":"")+l+"\n"); |
} |
|
}(); |
|
|
|
|
|
object SQL = class { |
object decode(array t, void|mapping options) |
{ |
|
return 0; |
} |
|
array encode(object t, void|mapping options) |
{ |
options = options||([]); |
string tablename = options->tablename||"sql_encode_default_table"; |
|
array queries = ({}); |
string fields = indices(t)*", "; |
foreach(values(t), array row) |
queries += ({ "insert into "+tablename+" ("+fields+") " |
"values("+ |
Array.map(row, lambda(mixed x) |
{ return "'"+(string)x+"'"; })*", "+ |
")" }); |
return queries; |
} |
}(); |
|
|