--- parser3/src/classes/table.C 2024/10/02 22:54:03 1.366 +++ parser3/src/classes/table.C 2026/04/25 13:38:46 1.383 @@ -1,7 +1,7 @@ /** @file Parser: @b table parser class. - Copyright (c) 2001-2023 Art. Lebedev Studio (http://www.artlebedev.com) + Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com) Authors: Konstantin Morshnev , Alexandr Petrosian */ @@ -24,8 +24,9 @@ #include "pa_sql_connection.h" #include "pa_vbool.h" #include "pa_array.h" +#include "pa_varray.h" -volatile const char * IDENT_TABLE_C="$Id: table.C,v 1.366 2024/10/02 22:54:03 moko Exp $"; +volatile const char * IDENT_TABLE_C="$Id: table.C,v 1.383 2026/04/25 13:38:46 moko Exp $"; // class @@ -403,7 +404,7 @@ static void skip_empty_lines( char** dat typedef void (*Skip_lines_action)(char** data_ref); static void _load(Request& r, MethodParams& params) { - const String& first_param=params.as_string(0, FILE_NAME_MUST_BE_STRING); + const String& first_param=params.as_file_name(0); int filename_param_index=0; bool nameless=first_param=="nameless"; if(nameless) @@ -416,7 +417,7 @@ static void _load(Request& r, MethodPara control_chars.load(*options); // loading text - char *data=file_load_text(r, r.full_disk_path(params.as_string(filename_param_index, FILE_NAME_MUST_BE_STRING)), true, options); + char *data=file_load_text(r, r.full_disk_path(params.as_file_name(filename_param_index)), true, options); Skip_lines_action skip_lines_action = (control_chars.separator=='#' || control_chars.encloser=='#') ? skip_empty_lines : skip_empty_and_comment_lines; @@ -623,7 +624,7 @@ static void table_to_csv(String& result, static void _save(Request& r, MethodParams& params) { - const String& first_arg=params.as_string(0, FIRST_ARG_MUST_NOT_BE_CODE); + const String& first_arg=params.as_string(0, PARAMETER_MUST_BE_STRING); size_t param_index=1; bool do_append=false; @@ -637,7 +638,7 @@ static void _save(Request& r, MethodPara else --param_index; - const String& file_name=params.as_string(param_index++, FILE_NAME_MUST_NOT_BE_CODE); + const String& file_name=params.as_file_name(param_index++); String file_spec=r.full_disk_path(file_name); if(do_append && file_exist(file_spec)) @@ -682,7 +683,7 @@ static void _csv_string(Request& r, Meth bool output_column_names=true; size_t param_index=0; if(params.count()>0 && params[0].is_string()) { - if(params.as_string(0, FIRST_ARG_MUST_NOT_BE_CODE)=="nameless") { + if(params.as_string(0, PARAM_MUST_NOT_BE_CODE)=="nameless") { output_column_names=false; param_index++; } else { @@ -750,7 +751,7 @@ static void _offset(Request& r, MethodPa else if(whence=="set") absolute=true; else - throw Exception(PARSER_RUNTIME, &whence, "is invalid whence, valid are 'cur' or 'set'"); + throw Exception(PARSER_RUNTIME, &whence, "is an invalid whence, valid are 'cur' or 'set'"); } int offset=params.as_int(params.count()-1, "offset must be expression", r); @@ -767,7 +768,7 @@ static void _menu(Request& r, MethodPara Value* delim_maybe_code=params.count()>1?¶ms[1]:0; Table& table=GET_SELF(r, VTable).table(); - size_t saved_current=table.current(); + Temp_current tc(table); if(delim_maybe_code) { // delimiter set bool need_delim=false; @@ -800,7 +801,6 @@ static void _menu(Request& r, MethodPara break; } } - table.set_current(saved_current); } #ifndef DOXYGEN @@ -849,11 +849,11 @@ static void table_row_to_hash(Table::ele for(Array_iterator i(*info->value_fields); i; ) { size_t value_field=i.next(); if(value_fieldcount()) - hash.put(columns ? *columns->get(value_field) : String(pa_uitoa(value_field)), new VString(*row->get(value_field))); + hash.put(columns ? *columns->get(value_field) : String::Body::uitoa(value_field), new VString(*row->get(value_field))); } } else { // all fields for(size_t index=0; indexcount(); index++) { - hash.put(columns && index < columns->count() ? *columns->get(index) : String(pa_uitoa(index)), new VString(*row->get(index))); + hash.put(columns && index < columns->count() ? *columns->get(index) : String::Body::uitoa(index), new VString(*row->get(index))); } } exist=info->hash->put_dont_replace(*key, vhash); @@ -1001,15 +1001,95 @@ static void _hash(Request& r, MethodPara }; info.key_field=(info.key_code ? -1 : self_table.column_name2index(key_param->as_string(), true)); - int saved_current=self_table.current(); - self_table.for_each(table_row_to_hash, &info); - self_table.set_current(saved_current); + Temp_current tc(self_table); + for(Table::RobustIterator i(self_table); i; i.next()) { + table_row_to_hash(i.value(), &info); + } result.extract_default(); r.write(result); } +static void _array(Request& r, MethodParams& params) { + Table& table=GET_SELF(r, VTable).table(); + VArray& result=*new VArray; + ArrayValue& array=result.array(); + + Table2hash_value_type value_type=C_HASH; + Array value_fields; + Value* value_code=0; + + if(params.count()>0) { + if(params[0].get_junction()){ + value_type=C_CODE; + value_code=¶ms[0]; + } else if(params[0].is_string()) { + const String &field_name=*params[0].get_string(); + if(!field_name.is_empty()){ + value_type=C_STRING; + value_fields+=table.column_name2index(field_name, true); + } + } else { + throw Exception(PARSER_RUNTIME, 0, "value field must be string or code or empty"); + } + } + + Temp_current tc(table); + for(Table::RobustIterator i(table); i; i.next()) { + switch(value_type) { + case C_STRING: { + size_t index=value_fields.get(0); + Table::element_type row=i.value(); + array+=(index < row->count() ? new VString(*row->get(index)) : VString::empty()); + break; + } + case C_HASH: { + VHash* vhash=new VHash; + HashStringValue& hash=vhash->hash(); + Table::element_type row=i.value(); + Table::columns_type columns=table.columns(); + for(size_t index=0; indexcount(); index++) { + hash.put(columns && index < columns->count() ? *columns->get(index) : String(pa_uitoa(index)), new VString(*row->get(index))); + } + array+=vhash; + break; + } + case C_CODE: { + table.set_current(i.index()); + array+=&r.process(*value_code); + break; + } + } + } + + r.write(result); +} + +static void _cells(Request& r, MethodParams& params) { + Table& self_table=GET_SELF(r, VTable).table(); + size_t row_size=self_table[self_table.current()]->count(); // number of columns in current row + + if(params.count()){ + int limit=params.as_int(params.count()-1, "limit must be expression", r); + if(limit<0) + limit=0; + if((size_t)limit3?¶ms[3]:0; Table& table=GET_SELF(r, VTable).table(); - size_t saved_current=table.current(); + Temp_current tc(table); rownum_var_name=rownum_var_name->is_empty()? 0 : rownum_var_name; value_var_name=value_var_name->is_empty()? 0 : value_var_name; @@ -1196,7 +1276,6 @@ static void _foreach(Request& r, MethodP break; } } - table.set_current(saved_current); } static void update_cell(HashStringValue::key_type aname, HashStringValue::value_type avalue, VTable *dest) { @@ -1222,14 +1301,16 @@ static void _append(Request& r, MethodPa HashStringValue* hash=params[0].get_hash(); if(hash){ + Temp_current tc(table); table+=new ArrayString(); - size_t saved_current=table.current(); table.set_current(table.count()-1); hash->for_each(update_cell, &vtable); - table.set_current(saved_current); } else { table+=row_from_string(r, params[0]); } + + if(table.count() > pa_array_limit) + throw Exception(PARSER_RUNTIME, 0, "table size (%d) exceeds limit $MAIN:LIMITS.max_array_size (%d)", table.count(), pa_array_limit); } static void _insert(Request& r, MethodParams& params) { @@ -1242,6 +1323,9 @@ static void _insert(Request& r, MethodPa } else { table.insert(table.current(), row_from_string(r, params[0])); } + + if(table.count() > pa_array_limit) + throw Exception(PARSER_RUNTIME, 0, "table size (%d) exceeds limit $MAIN:LIMITS.max_array_size (%d)", table.count(), pa_array_limit); } static void _delete(Request& r, MethodParams&) { @@ -1361,7 +1445,7 @@ void unmarshal_bind_updates(HashStringVa if(ph->is_null) value=VVoid::get(); else - value=new VString(*new String(ph->value, String::L_TAINTED)); + value=new VString(ph->value); hash.put(ph->name, value); } } @@ -1477,7 +1561,7 @@ static void _select(Request& r, MethodPa if(offset<0) offset+=size; if(size && limit>0 && offset>=0 && (size_t)offset (size_t)offset) // ...condition is true, adding to the result + if(condition && ++appended > (size_t)offset && source_table.valid(row)) // ...condition is true, adding to the result result_table+=source_table[row]; if(row==0) break; } @@ -1502,11 +1586,10 @@ static void _select(Request& r, MethodPa if(r.check_skip_break()) break; - if(condition && ++appended > (size_t)offset) // ...condition is true, adding to the result + if(condition && ++appended > (size_t)offset && source_table.valid(row)) // ...condition is true, adding to the result result_table+=source_table[row]; } } - source_table.set_current(saved_current); } r.write(*new VTable(&result_table)); @@ -1527,7 +1610,7 @@ static void _rename(Request& r, MethodPa Table& table=GET_SELF(r, VTable).table(); if(Table::columns_type columns=table.columns()) { if(names){ - for(int i=0; icount(); i++) { + for(size_t i=0; icount(); i++) { const String *column = columns->get(i); if(Value* vto=names->get(*column)){ if(const String *sto=vto->get_string()) @@ -1537,7 +1620,7 @@ static void _rename(Request& r, MethodPa } } } else if(name_from){ - for(int i=0; icount(); i++) { + for(size_t i=0; icount(); i++) { const String *column = columns->get(i); if(*column == *name_from) columns->put(i, name_to); @@ -1595,6 +1678,15 @@ MTable::MTable(): Methoded("table") { // ^table.hash[key field name][value field name(s) string/table] add_native_method("hash", Method::CT_DYNAMIC, _hash, 1, 3); + // ^table.array[] + // ^table.array[string] + // ^table.array{code} + add_native_method("array", Method::CT_DYNAMIC, _array, 0, 1); + + // ^table.cells[] + // ^table.cells(limit) + add_native_method("cells", Method::CT_DYNAMIC, _cells, 0, 1); + // ^table.sort{string-key-maker} ^table.sort{string-key-maker}[desc|asc] // ^table.sort(numeric-key-maker) ^table.sort(numeric-key-maker)[desc|asc] add_native_method("sort", Method::CT_DYNAMIC, _sort, 1, 2);