--- parser3/src/classes/hash.C 2021/11/04 21:31:46 1.152 +++ parser3/src/classes/hash.C 2024/10/27 17:50:59 1.166 @@ -1,8 +1,8 @@ /** @file Parser: @b hash parser class. - Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com) - Author: Alexandr Petrosian (http://paf.design.ru) + Copyright (c) 2001-2023 Art. Lebedev Studio (http://www.artlebedev.com) + Authors: Konstantin Morshnev , Alexandr Petrosian */ #include "classes.h" @@ -11,13 +11,14 @@ #include "pa_request.h" #include "pa_charsets.h" #include "pa_vhash.h" +#include "pa_varray.h" #include "pa_vvoid.h" #include "pa_sql_connection.h" #include "pa_vtable.h" #include "pa_vbool.h" #include "pa_vmethod_frame.h" -volatile const char * IDENT_HASH_C="$Id: hash.C,v 1.152 2021/11/04 21:31:46 moko Exp $"; +volatile const char * IDENT_HASH_C="$Id: hash.C,v 1.166 2024/10/27 17:50:59 moko Exp $"; // class @@ -38,23 +39,22 @@ DECLARE_CLASS_VAR(hash, new MHash); #ifndef DOXYGEN class Hash_sql_event_handlers: public SQL_Driver_query_event_handlers { bool distinct; - HashStringValue& rows_hash; + HashStringValue& result; Value* row_value; int column_index; - ArrayString& columns; + ArrayString* columns; bool one_bool_column; - static VBool only_one_column_value; Table2hash_value_type value_type; int columns_count; public: Table* empty; public: - Hash_sql_event_handlers(bool adistinct, HashStringValue& arows_hash, Table2hash_value_type avalue_type): + Hash_sql_event_handlers(bool adistinct, HashStringValue& aresult, Table2hash_value_type avalue_type): distinct(adistinct), - rows_hash(arows_hash), + result(aresult), row_value(0), column_index(0), - columns(*new ArrayString), + columns(new ArrayString), one_bool_column(false), value_type(avalue_type), empty(0) { @@ -62,7 +62,12 @@ public: bool add_column(SQL_Error& error, const char* str, size_t ) { try { - columns+=new String(str, String::L_TAINTED /* no length as 0x00 can be inside */); + if(columns_count){ + // another query in multi_statements mode + columns=new ArrayString; + columns_count=0; + } + *columns+=new String(str, String::L_TAINTED /* no length as 0x00 can be inside */); return false; } catch(...) { error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_column"); @@ -71,24 +76,27 @@ public: } bool before_rows(SQL_Error& error) { - if(columns.count()<1) { + columns_count=columns->count(); + if(columns_count<1) { error=SQL_Error("no columns"); return true; } - switch(value_type){ - case C_STRING: { - if(columns.count()>2){ - error=SQL_Error("only 2 columns allowed for $.type[string]."); - return true; + if(columns_count==1) { + one_bool_column=true; + } else { + switch(value_type){ + case C_STRING: { + if(columns_count>2){ + error=SQL_Error("only 2 columns allowed for $.type[string]"); + return true; + } + break; + } + case C_TABLE: { + // create empty table which we'll copy later + empty=new Table(columns); + break; } - } - case C_TABLE: { - // create empty table which we'll copy later - empty=new Table(&columns); - columns_count=columns.count(); - } - case C_HASH: { - one_bool_column=columns.count()==1; } } return false; @@ -103,51 +111,54 @@ public: try { const String& cell=str ? *new String(str, String::L_TAINTED /* no length as 0x00 can be inside */) : String::Empty; + if(column_index==columns_count){ + // should never happen, buggy driver case + error=SQL_Error("columns index exceed the columns count"); + return true; + } + bool duplicate=false; if(one_bool_column) { - duplicate=rows_hash.put_dont_replace(cell, &only_one_column_value); // put. existed? + duplicate=result.put_dont_replace(cell, &VBool::get(true)); // put. existed? } else if(column_index==0) { switch(value_type){ case C_HASH: { VHash* row_vhash=new VHash; row_value=row_vhash; - duplicate=rows_hash.put_dont_replace(cell, row_vhash); // put. existed? + duplicate=result.put_dont_replace(cell, row_vhash); // put. existed? break; } case C_STRING: { VString* row_vstring=new VString(); row_value=row_vstring; - duplicate=rows_hash.put_dont_replace(cell, row_vstring); // put. existed? + duplicate=result.put_dont_replace(cell, row_vstring); // put. existed? break; } case C_TABLE: { - VTable* vtable=(VTable*)rows_hash.get(cell); - Table* table; + VTable* vtable=(VTable*)result.get(cell); if(vtable) { // table with this key exist? if(!distinct) { duplicate=true; break; } - table=vtable->get_table(); } else { // no? creating table of same structure as source Table::Action_options table_options(0, 0); - table=new Table(*empty, table_options/*no rows, just structure*/); - vtable=new VTable(table); - rows_hash.put(cell, vtable); // put + vtable=new VTable(new Table(*empty, table_options/*no rows, just structure*/)); + result.put(cell, vtable); // put } ArrayString* row=new ArrayString(columns_count); row_value=(Value*)row; *row+=&cell; - *table+=row; + *vtable->get_table()+=row; break; } } } else { switch(value_type) { case C_HASH: { - row_value->get_hash()->put(*columns[column_index], new VString(cell)); + row_value->get_hash()->put(*columns->get(column_index), new VString(cell)); break; } case C_STRING: { @@ -177,7 +188,6 @@ public: } }; -VBool Hash_sql_event_handlers::only_one_column_value(true); #endif @@ -188,7 +198,13 @@ static void _create_or_add(Request& r, M HashStringValue* self_hash=&(self.hash()); HashStringValue* src_hash; - if(VHashBase* src=static_cast(vsrc.as(VHASH_TYPE))) { + if(VArray* src=dynamic_cast(&vsrc)) { + for(ArrayValue::Iterator i(src->array()); i; i.next()){ + if(i.value()) + self_hash->put(i.key(), i.value()); + } + return; + } else if(VHashBase* src=dynamic_cast(&vsrc)) { src_hash=&(src->hash()); if(src_hash==self_hash) // same: doing nothing @@ -197,6 +213,7 @@ static void _create_or_add(Request& r, M if(Value* vdefault=src->get_default()) self.set_default(vdefault); } else { + // allows $h[^hash::create[non-blank string]], thus as_hash("param") is more correct, but is not backward compatible src_hash=vsrc.get_hash(); } @@ -238,19 +255,46 @@ struct Copy_intersection_to_info { HashStringValue* dest; }; #endif -static void copy_intersection_to(HashStringValue::key_type key, HashStringValue::value_type value, Copy_intersection_to_info *info) { + +static void copy_intersection_by_arg(HashStringValue::key_type key, HashStringValue::value_type, Copy_intersection_to_info *info) { + if(HashStringValue::value_type value=info->b->get(key)) + info->dest->put_dont_replace(key, value); +} + +static void copy_intersection_by_self(HashStringValue::key_type key, HashStringValue::value_type value, Copy_intersection_to_info *info) { if(info->b->get(key)) info->dest->put_dont_replace(key, value); } + static void _intersection(Request& r, MethodParams& params) { Value& result=*new VHash; - // dest += b + + bool order_by_arg=false; + if(params.count()>1) + if(HashStringValue* options=params.as_hash(1, "options")) { + int valid_options=0; + if(Value* vorder=options->get("order")) { + const String &sorder=r.process(*vorder).as_string(); + if(sorder == "arg") + order_by_arg=true; + else if(sorder != "self") + throw Exception(PARSER_RUNTIME, &sorder, "'order' must be 'self' or 'arg'"); + valid_options++; + } + if(valid_options!=options->count()) + throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); + } + if(HashStringValue* b=params.as_hash(0, "param")) { - Copy_intersection_to_info info={b, result.get_hash()}; - GET_SELF(r, VHashBase).hash().for_each(copy_intersection_to, &info); + if(order_by_arg){ + Copy_intersection_to_info info={&GET_SELF(r, VHashBase).hash(), result.get_hash()}; + b->for_each(copy_intersection_by_arg, &info); + } else { + Copy_intersection_to_info info={b, result.get_hash()}; + GET_SELF(r, VHashBase).hash().for_each(copy_intersection_by_self, &info); + } } - // return result r.write(result); } @@ -290,25 +334,25 @@ static void _sql(Request& r, MethodParam if(params.count()>1) if(HashStringValue* options=params.as_hash(1, "sql options")) { int valid_options=0; - if(Value* vbind=options->get(sql_bind_name)) { - valid_options++; - bind=vbind->get_hash(); - } - if(Value* vlimit=options->get(sql_limit_name)) { - valid_options++; - limit=(ulong)r.process(*vlimit).as_double(); - } - if(Value* voffset=options->get(sql_offset_name)) { - valid_options++; - offset=(ulong)r.process(*voffset).as_double(); - } - if(Value* vdistinct=options->get(sql_distinct_name)) { - valid_options++; - distinct=r.process(*vdistinct).as_bool(); - } - if(Value* vvalue_type=options->get(sql_value_type_name)) { - valid_options++; - value_type=get_value_type(r.process(*vvalue_type)); + for(HashStringValue::Iterator i(*options); i; i.next() ){ + String::Body key=i.key(); + Value* value=i.value(); + if(key == sql_bind_name) { + bind=value->get_hash(); + valid_options++; + } else if(key == sql_limit_name) { + limit=(ulong)r.process(*value).as_double(); + valid_options++; + } else if(key == sql_offset_name) { + offset=(ulong)r.process(*value).as_double(); + valid_options++; + } else if (key == sql_distinct_name) { + distinct=r.process(*value).as_bool(); + valid_options++; + } else if (key == sql_value_type_name) { + value_type=get_value_type(r.process(*value)); + valid_options++; + } } if(valid_options!=options->count()) throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); @@ -436,7 +480,7 @@ enum AtResultType { AtResultTypeHash = 2 }; -inline Value& SingleElementHash(String::Body akey, Value* avalue) { +static Value& SingleElementHash(String::Body akey, Value* avalue) { Value& result=*new VHash; result.put_element(*new String(akey, String::L_TAINTED), avalue); return result; @@ -528,8 +572,6 @@ static void _at(Request& r, MethodParams HashStringValue& hash=GET_SELF(r, VHashBase).hash(); size_t count=hash.count(); - int pos=0; - // misha@ // I do not like that type is checked before whence. // But I do not like the idea to move it after whence (where process can be called) even more. @@ -544,20 +586,7 @@ static void _at(Request& r, MethodParams throw Exception(PARSER_RUNTIME, &stype, "type must be 'key', 'value' or 'hash'"); } - Value& vwhence=params[0]; - if(vwhence.is_string()) { - const String& swhence=*vwhence.get_string(); - if(swhence == "last") - pos=count-1; - else if(swhence != "first") - throw Exception(PARSER_RUNTIME, - &swhence, - "whence must be 'first', 'last' or expression"); - } else { - pos=r.process(vwhence).as_int(); - if(pos < 0) - pos+=count; - } + int pos=params.as_index(0, count, r); if(count && pos >= 0 && (size_t)pos < count){ switch(result_type) { @@ -622,6 +651,7 @@ static void _at(Request& r, MethodParams extern String table_reverse_name; static void _select(Request& r, MethodParams& params) { + InCycle temp(r); const String* key_var_name=¶ms.as_string(0, "key-var name must be string"); const String* value_var_name=¶ms.as_string(1, "value-var name must be string"); Value& vcondition=params.as_expression(2, "condition must be number, bool or expression"); @@ -658,6 +688,7 @@ static void _select(Request& r, MethodPa HashStringValue& result_hash=*new HashStringValue(); if(limit>0){ + #ifdef HASH_ORDER if(reverse){ for(HashStringValue::ReverseIterator i(source_hash); i; i.prev()){ @@ -666,7 +697,12 @@ static void _select(Request& r, MethodPa if(value_var_name) r.put_element(caller, *value_var_name, i.value()); - if(r.process(vcondition).as_bool()){ + bool condition=r.process(vcondition).as_bool(); + + if(r.check_skip_break()) + break; + + if(condition){ result_hash.put(i.key(), i.value()); if(!--limit) break; @@ -682,7 +718,12 @@ static void _select(Request& r, MethodPa if(value_var_name) r.put_element(caller, *value_var_name, i.value()); - if(r.process(vcondition).as_bool()){ + bool condition=r.process(vcondition).as_bool(); + + if(r.check_skip_break()) + break; + + if(condition){ result_hash.put(i.key(), i.value()); if(!--limit) break; @@ -729,7 +770,7 @@ static void _rename(Request& r, MethodPa hash.rename(key_from, key_to); } else { - HashStringValue* names=params.as_hash(0); + HashStringValue* names=params.as_hash(0,"single parameter"); for(HashStringValue::Iterator i(*names); i; i.next()) hash.rename(i.key(), i.value()->as_string()); @@ -749,8 +790,8 @@ MHash::MHash(): Methoded("hash") add_native_method("sub", Method::CT_DYNAMIC, _sub, 1, 1); // ^a.union[b] = hash add_native_method("union", Method::CT_DYNAMIC, _union, 1, 1); - // ^a.intersection[b] = hash - add_native_method("intersection", Method::CT_DYNAMIC, _intersection, 1, 1); + // ^a.intersection[b][options hash] = hash + add_native_method("intersection", Method::CT_DYNAMIC, _intersection, 1, 2); // ^a.intersects[b] = bool add_native_method("intersects", Method::CT_DYNAMIC, _intersects, 1, 1);