|
|
1.1 moko 1: /** @file
2: Parser: @b array parser class.
3:
4: Copyright (c) 2001-2023 Art. Lebedev Studio (http://www.artlebedev.com)
5: Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
6: */
7:
8: #include "classes.h"
9: #include "pa_vmethod_frame.h"
10:
11: #include "pa_request.h"
12: #include "pa_charsets.h"
13: #include "pa_varray.h"
14: #include "pa_vvoid.h"
15: #include "pa_sql_connection.h"
16: #include "pa_vtable.h"
17: #include "pa_vbool.h"
18: #include "pa_vmethod_frame.h"
19:
1.11 ! moko 20: volatile const char * IDENT_ARRAY_C="$Id: array.C,v 1.10 2024/09/22 18:23:22 moko Exp $";
1.1 moko 21:
22: // class
23:
24: class MArray: public Methoded {
25: public: // VStateless_class
26: Value* create_new_value(Pool&) { return new VArray; }
27:
28: public:
29: MArray();
30: };
31:
32: // global variable
33:
34: DECLARE_CLASS_VAR(array, new MArray);
35:
1.5 moko 36: const char* const PARAM_ARRAY_OR_HASH = "param must be array or hash";
1.7 moko 37: const char* const PARAM_INDEX = "index must be integer";
1.5 moko 38:
1.1 moko 39: // methods
40:
41: static void _create_or_add(Request& r, MethodParams& params) {
42: if(params.count()) {
1.5 moko 43: Value& vsrc=params.as_no_junction(0, PARAM_ARRAY_OR_HASH);
1.1 moko 44: VArray& self=GET_SELF(r, VArray);
45: ArrayValue& self_array=self.array();
46:
1.3 moko 47: if(VArray* src=dynamic_cast<VArray*>(&vsrc)) {
1.6 moko 48: if(src==&self)
49: throw Exception(PARSER_RUNTIME, 0, "source and destination are the same array");
1.5 moko 50: self_array.append(src->array());
1.1 moko 51: } else {
52: HashStringValue* src_hash=vsrc.get_hash();
1.5 moko 53: if(!src_hash)
54: return;
55: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
1.7 moko 56: self_array.put(VArray::index(i.key()), i.value());
1.5 moko 57: }
1.1 moko 58: }
1.4 moko 59: self.invalidate();
1.1 moko 60: }
61: }
62:
1.6 moko 63: static ArrayValue::Action_options get_action_options(Request& r, MethodParams& params, size_t options_index) {
64: ArrayValue::Action_options result;
65: if(params.count() <= options_index)
66: return result;
67:
68: HashStringValue* options=params.as_hash(options_index);
69: if(!options)
70: return result;
71:
72: result.defined=true;
73: int valid_options=0;
74:
75: if(Value* voffset=options->get(sql_offset_name)) {
76: valid_options++;
1.8 moko 77: int offset=r.process(*voffset).as_int();
78: result.offset=offset < 0 ? 0 : offset;
1.6 moko 79: }
80: if(Value* vlimit=options->get(sql_limit_name)) {
81: valid_options++;
1.8 moko 82: int limit=r.process(*vlimit).as_int();
83: result.limit=limit < 0 ? 0: limit;
1.6 moko 84: }
85:
86: if(valid_options!=options->count())
87: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
88:
89: return result;
90: }
91:
92: static void _join(Request& r, MethodParams& params) {
1.7 moko 93: Value& vsrc=params.as_no_junction(0, PARAM_ARRAY_OR_HASH);
1.6 moko 94: ArrayValue::Action_options o=get_action_options(r, params, 1);
95:
96: VArray& self=GET_SELF(r, VArray);
97: ArrayValue& self_array=self.array();
98:
99: if(VArray* src=dynamic_cast<VArray*>(&vsrc)) {
100: if(src==&self)
101: throw Exception(PARSER_RUNTIME, 0, "source and destination are the same array");
102:
103: if(o.defined){
104: for(ArrayValue::Iterator i(src->array()); i; i.next()){
105: if(i.value()){
106: if(o.offset > 0){
107: o.offset--;
108: continue;
109: }
110: if(o.limit-- == 0)
111: break;
112: self_array+=i.value();
113: }
114: }
115: } else {
116: for(ArrayValue::Iterator i(src->array()); i; i.next()){
117: if(i.value())
118: self_array+=i.value();
119: }
120: }
1.7 moko 121: } else {
122: HashStringValue* src_hash=vsrc.get_hash();
123: if(!src_hash)
124: return;
125: if(o.defined){
126: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
127: if(o.offset > 0){
128: o.offset--;
129: continue;
130: }
131: if(o.limit-- == 0)
132: break;
133: self_array+=i.value();
134: }
135: } else {
136: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
137: self_array+=i.value();
138: }
139: }
140: }
141: self.invalidate();
1.6 moko 142: }
143:
1.11 ! moko 144: #ifndef DOXYGEN
! 145:
! 146: #define STRING(str) ((str) ? *new String(str, String::L_TAINTED /* no length as 0x00 can be inside */) : String::Empty)
! 147:
! 148: class SparseArray_sql_event_handlers: public SQL_Driver_query_event_handlers {
! 149: bool distinct;
! 150: ArrayValue& result;
! 151: Value* row_value;
! 152: int column_index;
! 153: ArrayString& columns;
! 154: bool one_bool_column;
! 155: Table2hash_value_type value_type;
! 156: int columns_count;
! 157: public:
! 158: Table* empty;
! 159: public:
! 160: SparseArray_sql_event_handlers(bool adistinct, ArrayValue& aresult, Table2hash_value_type avalue_type):
! 161: distinct(adistinct),
! 162: result(aresult),
! 163: row_value(0),
! 164: column_index(0),
! 165: columns(*new ArrayString),
! 166: one_bool_column(false),
! 167: value_type(avalue_type),
! 168: empty(0) {
! 169: }
! 170:
! 171: bool add_column(SQL_Error& error, const char* str, size_t ) {
! 172: try {
! 173: columns+=&STRING(str);
! 174: return false;
! 175: } catch(...) {
! 176: error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_column");
! 177: return true;
! 178: }
! 179: }
! 180:
! 181: bool before_rows(SQL_Error& error) {
! 182: if(columns.count()<1) {
! 183: error=SQL_Error("no columns");
! 184: return true;
! 185: }
! 186: if(columns.count()==1) {
! 187: one_bool_column=true;
! 188: } else {
! 189: switch(value_type){
! 190: case C_STRING: {
! 191: if(columns.count()>2){
! 192: error=SQL_Error("only 2 columns allowed for $.type[string] and $.sparse(true).");
! 193: return true;
! 194: }
! 195: break;
! 196: }
! 197: case C_TABLE: {
! 198: // create empty table which we'll copy later
! 199: empty=new Table(&columns);
! 200: columns_count=columns.count();
! 201: break;
! 202: }
! 203: }
! 204: }
! 205: return false;
! 206: }
! 207:
! 208: bool add_row(SQL_Error& /*error*/) {
! 209: column_index=0;
! 210: return false;
! 211: }
! 212:
! 213: bool add_row_cell(SQL_Error& error, const char *str, size_t ) {
! 214: try {
! 215: bool duplicate=false;
! 216: if(one_bool_column) {
! 217: size_t index=str ? pa_atoui(str) : 0;
! 218: duplicate=result.put_dont_replace(index, &VBool::get(true)); // put. existed?
! 219: } else if(column_index==0) {
! 220: size_t index=str ? pa_atoui(str) : 0;
! 221: switch(value_type){
! 222: case C_HASH: {
! 223: VHash* row_vhash=new VHash;
! 224: row_value=row_vhash;
! 225: duplicate=result.put_dont_replace(index, row_vhash); // put. existed?
! 226: break;
! 227: }
! 228: case C_STRING: {
! 229: VString* row_vstring=new VString();
! 230: row_value=row_vstring;
! 231: duplicate=result.put_dont_replace(index, row_vstring); // put. existed?
! 232: break;
! 233: }
! 234: case C_TABLE: {
! 235: VTable* vtable=(VTable*)result.get(index);
! 236:
! 237: if(vtable) { // table with this key exist?
! 238: if(!distinct) {
! 239: duplicate=true;
! 240: break;
! 241: }
! 242: } else {
! 243: // no? creating table of same structure as source
! 244: Table::Action_options table_options(0, 0);
! 245: vtable=new VTable(new Table(*empty, table_options/*no rows, just structure*/));
! 246: result.put(index, vtable); // put
! 247: }
! 248: ArrayString* row=new ArrayString(columns_count);
! 249: *row+=&STRING(str);
! 250: *vtable->get_table()+=row;
! 251: break;
! 252: }
! 253: }
! 254: } else {
! 255: const String& cell=STRING(str);
! 256: switch(value_type) {
! 257: case C_HASH: {
! 258: row_value->get_hash()->put(*columns[column_index], new VString(cell));
! 259: break;
! 260: }
! 261: case C_STRING: {
! 262: VString* row_string=(VString*)row_value;
! 263: row_string->set_string(cell);
! 264: break;
! 265: }
! 266: case C_TABLE: {
! 267: ArrayString* row=(ArrayString*)row_value;
! 268: *row+=&cell;
! 269: break;
! 270: }
! 271: }
! 272: }
! 273:
! 274: if(duplicate & !distinct) {
! 275: error=SQL_Error("duplicate key");
! 276: return true;
! 277: }
! 278:
! 279: column_index++;
! 280: return false;
! 281: } catch(const Exception& e) {
! 282: error=SQL_Error(e.type(), e.comment());
! 283: return true;
! 284: } catch(...) {
! 285: error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_row_cell");
! 286: return true;
! 287: }
! 288: }
! 289: };
! 290:
! 291: class Array_sql_event_handlers: public SQL_Driver_query_event_handlers {
! 292: ArrayValue& result;
! 293: Value* row_value;
! 294: int column_index;
! 295: ArrayString& columns;
! 296: Table2hash_value_type value_type;
! 297: int columns_count;
! 298: public:
! 299: Table* empty;
! 300: public:
! 301: Array_sql_event_handlers(ArrayValue& aresult, Table2hash_value_type avalue_type):
! 302: result(aresult),
! 303: row_value(0),
! 304: column_index(0),
! 305: columns(*new ArrayString),
! 306: value_type(avalue_type),
! 307: empty(0) {
! 308: }
! 309:
! 310: bool add_column(SQL_Error& error, const char* str, size_t ) {
! 311: try {
! 312: columns+=&STRING(str);
! 313: return false;
! 314: } catch(...) {
! 315: error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_column");
! 316: return true;
! 317: }
! 318: }
! 319:
! 320: bool before_rows(SQL_Error& error) {
! 321: if(columns.count()<1) {
! 322: error=SQL_Error("no columns");
! 323: return true;
! 324: }
! 325: switch(value_type){
! 326: case C_STRING: {
! 327: if(columns.count()>1){
! 328: error=SQL_Error("only one column allowed for $.type[string].");
! 329: return true;
! 330: }
! 331: break;
! 332: }
! 333: case C_TABLE: {
! 334: // create empty table which we'll copy later
! 335: empty=new Table(&columns);
! 336: columns_count=columns.count();
! 337: break;
! 338: }
! 339: }
! 340: return false;
! 341: }
! 342:
! 343: bool add_row(SQL_Error& /*error*/) {
! 344: column_index=0;
! 345: return false;
! 346: }
! 347:
! 348: bool add_row_cell(SQL_Error& error, const char *str, size_t ) {
! 349: try {
! 350: if(column_index==0) {
! 351: switch(value_type){
! 352: case C_HASH: {
! 353: VHash* row_vhash=new VHash;
! 354: row_value=row_vhash;
! 355: result+=row_vhash;
! 356: break;
! 357: }
! 358: case C_STRING: {
! 359: VString* row_vstring=new VString();
! 360: row_value=row_vstring;
! 361: result+=row_vstring;
! 362: break;
! 363: }
! 364: case C_TABLE: {
! 365: // creating table of same structure as source
! 366: Table::Action_options table_options(0, 0);
! 367: VTable* vtable=new VTable(new Table(*empty, table_options/*no rows, just structure*/));
! 368: result+=vtable;
! 369:
! 370: ArrayString* row=new ArrayString(columns_count);
! 371: *vtable->get_table()+=row;
! 372: break;
! 373: }
! 374: }
! 375: }
! 376:
! 377: const String& cell=STRING(str);
! 378: switch(value_type) {
! 379: case C_HASH: {
! 380: row_value->get_hash()->put(*columns[column_index], new VString(cell));
! 381: break;
! 382: }
! 383: case C_STRING: {
! 384: VString* row_string=(VString*)row_value;
! 385: row_string->set_string(cell);
! 386: break;
! 387: }
! 388: case C_TABLE: {
! 389: ArrayString* row=(ArrayString*)row_value;
! 390: *row+=&cell;
! 391: break;
! 392: }
! 393: }
! 394:
! 395: column_index++;
! 396: return false;
! 397: } catch(const Exception& e) {
! 398: error=SQL_Error(e.type(), e.comment());
! 399: return true;
! 400: } catch(...) {
! 401: error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_row_cell");
! 402: return true;
! 403: }
! 404: }
! 405: };
! 406:
! 407: #endif
! 408:
! 409: extern Table2hash_value_type get_value_type(Value& vvalue_type);
! 410: extern int marshal_binds(HashStringValue& hash, SQL_Driver::Placeholder*& placeholders);
! 411: extern void unmarshal_bind_updates(HashStringValue& hash, int placeholder_count, SQL_Driver::Placeholder* placeholders);
! 412:
! 413: static void _sql(Request& r, MethodParams& params) {
! 414: Value& statement=params.as_junction(0, "statement must be code");
! 415:
! 416: HashStringValue* bind=0;
! 417: ulong limit=SQL_NO_LIMIT;
! 418: ulong offset=0;
! 419: bool distinct=false;
! 420: bool sparse=false;
! 421: Table2hash_value_type value_type=C_HASH;
! 422: if(params.count()>1)
! 423: if(HashStringValue* options=params.as_hash(1, "sql options")) {
! 424: int valid_options=0;
! 425: for(HashStringValue::Iterator i(*options); i; i.next() ){
! 426: String::Body key=i.key();
! 427: Value* value=i.value();
! 428: if(key == sql_bind_name) {
! 429: bind=value->get_hash();
! 430: valid_options++;
! 431: } else if(key == sql_limit_name) {
! 432: limit=(ulong)r.process(*value).as_double();
! 433: valid_options++;
! 434: } else if(key == sql_offset_name) {
! 435: offset=(ulong)r.process(*value).as_double();
! 436: valid_options++;
! 437: } else if (key == sql_distinct_name) {
! 438: distinct=r.process(*value).as_bool();
! 439: valid_options++;
! 440: } else if (key == sql_value_type_name) {
! 441: sparse=r.process(*value).as_bool();
! 442: valid_options++;
! 443: } else if (key == "sparse") {
! 444: value_type=get_value_type(r.process(*value));
! 445: valid_options++;
! 446: }
! 447: }
! 448: if(valid_options!=options->count())
! 449: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
! 450: }
! 451:
! 452: SQL_Driver::Placeholder* placeholders=0;
! 453: uint placeholders_count=0;
! 454: if(bind)
! 455: placeholders_count=marshal_binds(*bind, placeholders);
! 456:
! 457: const String& statement_string=r.process_to_string(statement);
! 458: const char* statement_cstr=statement_string.untaint_cstr(String::L_SQL, r.connection());
! 459:
! 460: VArray& self=GET_SELF(r, VArray);
! 461:
! 462: self.array().clear(); self.invalidate(); // just in case if called as method
! 463:
! 464: if(sparse){
! 465: SparseArray_sql_event_handlers handlers(distinct, self.array(), value_type);
! 466: r.connection()->query(statement_cstr, placeholders_count, placeholders, offset, limit, handlers, statement_string);
! 467: } else {
! 468: Array_sql_event_handlers handlers(self.array(), value_type);
! 469: r.connection()->query(statement_cstr, placeholders_count, placeholders, offset, limit, handlers, statement_string);
! 470: }
! 471:
! 472: if(bind)
! 473: unmarshal_bind_updates(*bind, placeholders_count, placeholders);
! 474: }
! 475:
1.1 moko 476:
477:
1.10 moko 478: static void mid(Request& r, size_t offset=0, size_t limit=ARRAY_OPTION_LIMIT_ALL) {
1.9 moko 479: ArrayValue& array=GET_SELF(r, VArray).array();
1.10 moko 480: if(limit>0){
1.9 moko 481: VArray *result=new VArray;
482: ArrayValue& result_array=result->array();
483: for(ArrayValue::Iterator i(array); i; i.next()){
484: if(i.value()){
1.10 moko 485: if(offset > 0){
486: offset--;
1.9 moko 487: continue;
488: }
1.10 moko 489: if(limit-- == 0)
1.9 moko 490: break;
491: result_array+=i.value();
492: }
493: }
494: r.write(*result);
495: } else {
496: r.write(*new VArray);
497: }
498: }
1.1 moko 499:
1.9 moko 500: static void _left(Request& r, MethodParams& params) {
501: int sn=params.as_int(0, "n must be int", r);
1.10 moko 502: mid(r, 0, sn < 0 ? 0 : sn);
1.9 moko 503: }
504:
505: static void _right(Request& r, MethodParams& params) {
506: int sn=params.as_int(0, "n must be int", r);
507:
508: if(sn>0){
509: size_t used=GET_SELF(r, VArray).array().used();
510: if(sn<used){
1.10 moko 511: mid(r, used-sn, sn);
1.9 moko 512: } else {
1.10 moko 513: mid(r);
1.9 moko 514: }
515: } else {
1.10 moko 516: mid(r, 0, 0);
1.9 moko 517: }
518: }
519:
520: static void _mid(Request& r, MethodParams& params) {
521: const String& string=GET_SELF(r, VString).string();
522:
523: int begin=params.as_int(0, "p must be int", r);
524: if(begin<0)
525: throw Exception(PARSER_RUNTIME, 0, "p(%d) must be >=0", begin);
526:
527: size_t end;
528: size_t length=0;
1.1 moko 529:
1.9 moko 530: if(params.count()>1) {
531: int n=params.as_int(1, "n must be int", r);
532: if(n<0)
533: throw Exception(PARSER_RUNTIME, 0, "n(%d) must be >=0", n);
1.10 moko 534: mid(r, begin, n);
1.9 moko 535: } else {
1.10 moko 536: mid(r, begin);
1.9 moko 537: }
538: }
1.1 moko 539:
540: static void _keys(Request& r, MethodParams& params) {
541: const String* keys_column_name;
542: if(params.count()>0)
543: keys_column_name=¶ms.as_string(0, COLUMN_NAME_MUST_BE_STRING);
544: else
545: keys_column_name=new String("key");
546:
547: Table::columns_type columns(new ArrayString(1));
548: *columns+=keys_column_name;
549: Table* table=new Table(columns);
550:
551: ArrayValue& array=GET_SELF(r, VArray).array();
552: for(ArrayValue::Iterator i(array); i; i.next()){
553: if(i.value()){
554: Table::element_type row(new ArrayString(1));
555: *row+=new String(i.key(), String::L_TAINTED);
556: *table+=row;
557: }
558: }
559:
560: r.write(*new VTable(table));
561: }
562:
1.5 moko 563: static void _count(Request& r, MethodParams& params) {
564: ArrayValue& array=GET_SELF(r, VArray).array();
565: if(params.count()>0){
566: const String& what=params.as_string(0, PARAMETER_MUST_BE_STRING);
567: if(!what.is_empty()){
568: if(what != "all")
569: throw Exception(PARSER_RUNTIME, &what, "param must be empty or 'all'");
570: return r.write(*new VInt(array.count()));
571: }
572: }
573: r.write(*new VInt(array.used()));
1.1 moko 574: }
575:
1.2 moko 576: static void _append(Request& r, MethodParams& params) {
577: VArray& self=GET_SELF(r, VArray);
578: ArrayValue& array=self.array();
579:
580: int count=params.count();
581:
582: for(int i=0; i<count; i++){
583: array+=&r.process(params[i]);
584: }
1.4 moko 585: self.invalidate();
1.2 moko 586: }
587:
588: static void _insert(Request& r, MethodParams& params) {
589: VArray& self=GET_SELF(r, VArray);
590: ArrayValue& array=self.array();
591:
592: int count=params.count();
1.7 moko 593: size_t index=VArray::index(params.as_int(0, PARAM_INDEX, r));
1.2 moko 594:
595: for(int i=1; i<count; i++){
1.8 moko 596: array.insert(index++, &r.process(params[i]));
1.2 moko 597: }
1.4 moko 598: self.invalidate();
1.2 moko 599: }
600:
1.1 moko 601: static void _delete(Request& r, MethodParams& params) {
1.7 moko 602: VArray& self=GET_SELF(r, VArray);
1.1 moko 603: if(params.count()>0)
1.7 moko 604: self.array().clear(VArray::index(params.as_int(0, PARAM_INDEX, r)));
1.1 moko 605: else
1.7 moko 606: self.array().clear();
607: self.invalidate();
608: }
609:
610: static void _remove(Request& r, MethodParams& params) {
611: VArray& self=GET_SELF(r, VArray);
612: self.array().remove(VArray::index(params.as_int(0, PARAM_INDEX, r)));
613: self.invalidate();
1.1 moko 614: }
615:
616: static void _contains(Request& r, MethodParams& params) {
617: VArray& self=GET_SELF(r, VArray);
1.7 moko 618: bool result=self.contains(VArray::index(params.as_int(0, PARAM_INDEX, r)));
1.1 moko 619: r.write(VBool::get(result));
620: }
621:
1.5 moko 622: static void _for(Request& r, MethodParams& params) {
623: InCycle temp(r);
624:
1.7 moko 625: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
626: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
627: Value* body_code=¶ms.as_junction(2, "body must be code");
628: Value* delim_maybe_code=params.count()>3 ? ¶ms[3] : 0;
1.5 moko 629: Value& caller=*r.get_method_frame()->caller();
630:
1.7 moko 631: if(key_var_name->is_empty()) key_var_name=0;
1.5 moko 632: if(value_var_name->is_empty()) value_var_name=0;
633:
634: ArrayValue& array=GET_SELF(r, VArray).array();
635:
636: if(delim_maybe_code){ // delimiter set
637: bool need_delim=false;
638: for(ArrayValue::Iterator i(array); i; i.next()){
1.7 moko 639: if(key_var_name){
640: VString* vkey=new VString(*new String(i.key(), String::L_TAINTED));
641: r.put_element(caller, *key_var_name, vkey);
642: }
643:
1.5 moko 644: if(value_var_name)
645: r.put_element(caller, *value_var_name, i.value() ? i.value() : VVoid::get());
646:
647: Value& sv_processed=r.process(*body_code);
648: TempSkip4Delimiter skip(r);
649:
650: const String* s_processed=sv_processed.get_string();
651: if(s_processed && !s_processed->is_empty()) { // we have body
652: if(need_delim) // need delim & iteration produced string?
653: r.write(r.process(*delim_maybe_code));
654: else
655: need_delim=true;
656: }
657:
658: r.write(sv_processed);
659:
660: if(skip.check_break())
661: break;
662: }
663: } else {
664: for(ArrayValue::Iterator i(array); i; i.next()){
1.7 moko 665: if(key_var_name){
666: VString* vkey=new VString(*new String(i.key(), String::L_TAINTED));
667: r.put_element(caller, *key_var_name, vkey);
668: }
669:
1.5 moko 670: if(value_var_name)
671: r.put_element(caller, *value_var_name, i.value() ? i.value() : VVoid::get());
672:
673: r.process_write(*body_code);
674:
675: if(r.check_skip_break())
676: break;
677: }
678: }
679: }
680:
1.1 moko 681: static void _foreach(Request& r, MethodParams& params) {
682: InCycle temp(r);
683:
684: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
685: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
686: Value* body_code=¶ms.as_junction(2, "body must be code");
1.7 moko 687: Value* delim_maybe_code=params.count()>3 ? ¶ms[3] : 0;
1.1 moko 688: Value& caller=*r.get_method_frame()->caller();
689:
690: if(key_var_name->is_empty()) key_var_name=0;
691: if(value_var_name->is_empty()) value_var_name=0;
692:
693: ArrayValue& array=GET_SELF(r, VArray).array();
694:
695: if(delim_maybe_code){ // delimiter set
696: bool need_delim=false;
697: for(ArrayValue::Iterator i(array); i; i.next()){
698: if(i.value()){
699: if(key_var_name){
700: VString* vkey=new VString(*new String(i.key(), String::L_TAINTED));
701: r.put_element(caller, *key_var_name, vkey);
702: }
703:
704: if(value_var_name)
705: r.put_element(caller, *value_var_name, i.value());
706:
707: Value& sv_processed=r.process(*body_code);
708: TempSkip4Delimiter skip(r);
709:
710: const String* s_processed=sv_processed.get_string();
711: if(s_processed && !s_processed->is_empty()) { // we have body
712: if(need_delim) // need delim & iteration produced string?
713: r.write(r.process(*delim_maybe_code));
714: else
715: need_delim=true;
716: }
717:
718: r.write(sv_processed);
719:
720: if(skip.check_break())
721: break;
722: }
723: }
724: } else {
725: for(ArrayValue::Iterator i(array); i; i.next()){
726: if(i.value()){
727: if(key_var_name){
728: VString* vkey=new VString(*new String(i.key(), String::L_TAINTED));
729: r.put_element(caller, *key_var_name, vkey);
730: }
731:
732: if(value_var_name)
733: r.put_element(caller, *value_var_name, i.value());
734:
735: r.process_write(*body_code);
736:
737: if(r.check_skip_break())
738: break;
739: }
740: }
741: }
742: }
743:
744: #ifndef DOXYGEN
745: struct Array_seq_item : public PA_Allocated {
746: Value *array_data;
747: union {
748: const char *c_str;
749: double d;
750: } value;
751: };
752: #endif
753:
754: static int sort_cmp_string(const void *a, const void *b) {
755: return strcmp(
756: static_cast<const Array_seq_item *>(a)->value.c_str,
757: static_cast<const Array_seq_item *>(b)->value.c_str
758: );
759: }
760: static int sort_cmp_double(const void *a, const void *b) {
761: double va=static_cast<const Array_seq_item *>(a)->value.d;
762: double vb=static_cast<const Array_seq_item *>(b)->value.d;
763: if(va<vb)
764: return -1;
765: else if(va>vb)
766: return +1;
767: else
768: return 0;
769: }
770:
771: static void _sort(Request& r, MethodParams& params){
772: const String& key_var_name=params.as_string(0, "key-var name must be string");
773: const String& value_var_name=params.as_string(1, "value-var name must be string");
774: Value& key_maker=params.as_junction(2, "key-maker must be code");
775: bool reverse=params.count()>3 && params.as_no_junction(3, "order must not be code").as_string()=="desc"; // default=asc
776:
777: const String* key_var=key_var_name.is_empty()? 0 : &key_var_name;
778: const String* value_var=value_var_name.is_empty()? 0 : &value_var_name;
779: VMethodFrame* context=r.get_method_frame()->caller();
780:
781: VArray& self=GET_SELF(r, VArray);
782: ArrayValue& array=self.array();
1.4 moko 783: int count=array.used(); // not array.count()
1.1 moko 784:
785: Array_seq_item* seq=new Array_seq_item[count];
786: int pos=0;
787: bool key_values_are_strings=true;
788:
789: for(ArrayValue::Iterator i(array); i; i.next() ){
790: if(i.value()){
791: if(key_var)
792: r.put_element(*context, *key_var, new VString(*new String(i.key(), String::L_TAINTED)));
793: if(value_var)
794: r.put_element(*context, *value_var, i.value());
795:
796: Value& value=r.process(key_maker);
797: if(pos==0) // determining key values type by first one
798: key_values_are_strings=value.is_string();
799:
800: seq[pos].array_data=i.value();
801: if(key_values_are_strings)
802: seq[pos++].value.c_str=value.as_string().cstr();
803: else
804: seq[pos++].value.d=value.as_expr_result().as_double();
805: }
806: }
807:
808: // @todo: handle this elsewhere
809: if(r.charsets.source().NAME()=="KOI8-R" && key_values_are_strings)
810: for(pos=0; pos<count; pos++)
811: if(*seq[pos].value.c_str)
812: seq[pos].value.c_str=Charset::transcode(seq[pos].value.c_str, r.charsets.source(), pa_UTF8_charset).cstr();
813:
814: // sort keys
815: qsort(seq, count, sizeof(Array_seq_item), key_values_are_strings ? sort_cmp_string : sort_cmp_double);
816:
817: // reorder array as required in 'seq'
818: array.clear();
819: if(reverse)
820: for(pos=count-1; pos>=0; pos--)
821: array+=seq[pos].array_data;
822: else
823: for(pos=0; pos<count; pos++)
824: array+=seq[pos].array_data;
825:
826: delete[] seq;
827: }
828:
1.5 moko 829: enum AtResultType {
830: AtResultTypeValue = 0,
831: AtResultTypeKey = 1,
832: AtResultTypeHash = 2
833: };
834:
835: inline Value& SingleElementHash(String::Body akey, Value* avalue) {
836: Value& result=*new VHash;
837: result.put_element(*new String(akey, String::L_TAINTED), avalue);
838: return result;
839: }
840:
1.1 moko 841: static void _at(Request& r, MethodParams& params) {
842: VArray& self=GET_SELF(r, VArray);
843: ArrayValue& array=self.array();
1.5 moko 844: size_t count=array.used(); // not array.count()
1.1 moko 845:
846: int pos=0;
847:
848: AtResultType result_type=AtResultTypeValue;
849: if(params.count() > 1) {
850: const String& stype=params.as_string(1, "type must be string");
851: if(stype == "key")
852: result_type=AtResultTypeKey;
853: else if(stype == "hash")
854: result_type=AtResultTypeHash;
855: else if(stype != "value")
856: throw Exception(PARSER_RUNTIME, &stype, "type must be 'key', 'value' or 'hash'");
857: }
858:
859: Value& vwhence=params[0];
860: if(vwhence.is_string()) {
861: const String& swhence=*vwhence.get_string();
862: if(swhence == "last")
863: pos=count-1;
864: else if(swhence != "first")
865: throw Exception(PARSER_RUNTIME, &swhence, "whence must be 'first', 'last' or expression");
866: } else {
867: pos=r.process(vwhence).as_int();
868: if(pos < 0)
869: pos+=count;
870: }
871:
872: if(count && pos >= 0 && (size_t)pos < count){
873: switch(result_type) {
874: case AtResultTypeKey:
875: {
876: for(ArrayValue::Iterator i(array); i; i.next() ){
877: if(i.value() && !(pos--)){
878: r.write(*new VString(*new String(i.key(), String::L_TAINTED)));
879: break;
880: }
881: }
882: break;
883: }
884: case AtResultTypeValue:
885: {
886: for(ArrayValue::Iterator i(array); i; i.next() )
887: if(i.value() &&!(pos--)){
888: r.write(*i.value());
889: break;
890: }
891: break;
892: }
893: case AtResultTypeHash:
894: {
895: for(ArrayValue::Iterator i(array); i; i.next() )
896: if(i.value() &&!(pos--)){
897: r.write(SingleElementHash(i.key(), i.value()));
898: break;
899: }
900: break;
901: }
902: }
903: }
904: }
905:
906:
907: extern String table_reverse_name;
908:
909: static void _select(Request& r, MethodParams& params) {
910: InCycle temp(r);
911: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
912: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
913: Value& vcondition=params.as_expression(2, "condition must be number, bool or expression");
914:
915: if(key_var_name->is_empty()) key_var_name=0;
916: if(value_var_name->is_empty()) value_var_name=0;
917:
918: ArrayValue& source_array=GET_SELF(r, VArray).array();
919: Value& caller=*r.get_method_frame()->caller();
920:
921: int limit=source_array.count();
922: bool reverse=false;
923:
924: if(params.count()>3)
925: if(HashStringValue* options=params.as_hash(3)) {
926: int valid_options=0;
927: if(Value* vlimit=options->get(sql_limit_name)) {
928: valid_options++;
929: limit=r.process(*vlimit).as_int();
930: }
931: if(Value* vreverse=options->get(table_reverse_name)) {
932: valid_options++;
933: reverse=r.process(*vreverse).as_bool();
934: }
935: if(valid_options!=options->count())
936: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
937: }
938:
939: VArray *result=new VArray;
940: ArrayValue& result_array=result->array();
941:
942: if(limit>0){
943: if(reverse){
944: for(ArrayValue::ReverseIterator i(source_array); i; ){
1.5 moko 945: if(Value *value=i.prev()){ // here for correct i.key()
946: if(key_var_name)
947: r.put_element(caller, *key_var_name, new VString(*new String(i.key(), String::L_TAINTED)));
948: if(value_var_name)
949: r.put_element(caller, *value_var_name, value);
1.1 moko 950:
1.5 moko 951: bool condition=r.process(vcondition).as_bool();
1.1 moko 952:
1.5 moko 953: if(r.check_skip_break())
954: break;
1.1 moko 955:
1.5 moko 956: if(condition){
957: result_array+=value;
958: if(!--limit)
959: break;
960: }
1.1 moko 961: }
962: }
963: } else {
964: for(ArrayValue::Iterator i(source_array); i; i.next() ){
1.5 moko 965: if(Value *value=i.value()){
1.1 moko 966: if(key_var_name)
967: r.put_element(caller, *key_var_name, new VString(*new String(i.key(), String::L_TAINTED)));
968: if(value_var_name)
969: r.put_element(caller, *value_var_name, value);
970:
971: bool condition=r.process(vcondition).as_bool();
972:
973: if(r.check_skip_break())
974: break;
975:
976: if(condition){
977: result_array+=value;
978: if(!--limit)
979: break;
980: }
981: }
982: }
983: }
984: }
985:
986: r.write(*result);
987: }
988:
989: static void _reverse(Request& r, MethodParams& params) {
1.5 moko 990: ArrayValue& source_array=GET_SELF(r, VArray).array();
1.1 moko 991:
1.5 moko 992: VArray& result=*new VArray(source_array.count());
1.1 moko 993: ArrayValue& result_array=result.array();
994:
1.4 moko 995: for(ArrayValue::ReverseIterator i(source_array); i; ){
996: result_array+=i.prev();
1.1 moko 997: }
998:
999: r.write(result);
1000: }
1001:
1002:
1003: // constructor
1004:
1005: MArray::MArray(): Methoded(VARRAY_TYPE) {
1006:
1007: // ^array::create[[copy_from]]
1008: add_native_method("create", Method::CT_DYNAMIC, _create_or_add, 0, 1);
1009: // ^array.add[add_from]
1010: add_native_method("add", Method::CT_DYNAMIC, _create_or_add, 1, 1);
1.6 moko 1011: // ^array.join[join_from[;options]]
1012: add_native_method("join", Method::CT_DYNAMIC, _join, 1, 2);
1.1 moko 1013:
1.9 moko 1014: // ^array.left(n)
1015: add_native_method("left", Method::CT_DYNAMIC, _left, 1, 1);
1016: // ^array.right(n)
1017: add_native_method("right", Method::CT_DYNAMIC, _right, 1, 1);
1018: // ^array.mid(p)
1019: // ^array.mid(p;n)
1020: add_native_method("mid", Method::CT_DYNAMIC, _mid, 1, 2);
1.1 moko 1021:
1.8 moko 1022: // ^array::new[value;value]
1023: add_native_method("new", Method::CT_DYNAMIC, _append, 0, 10000);
1.2 moko 1024: // ^array.append[value;value]
1025: add_native_method("append", Method::CT_DYNAMIC, _append, 1, 10000);
1026: // ^array.insert[index;value...]
1027: add_native_method("insert", Method::CT_DYNAMIC, _insert, 2, 10000);
1028:
1029: // ^array.delete[index]
1.1 moko 1030: add_native_method("delete", Method::CT_DYNAMIC, _delete, 0, 1);
1.7 moko 1031: // ^array.remove[index]
1032: add_native_method("remove", Method::CT_DYNAMIC, _remove, 1, 1);
1.1 moko 1033:
1.2 moko 1034: // ^array.contains[index]
1.1 moko 1035: add_native_method("contains", Method::CT_DYNAMIC, _contains, 1, 1);
1036:
1037: // ^array::sql[query][options array]
1038: add_native_method("sql", Method::CT_DYNAMIC, _sql, 1, 2);
1039:
1040: // ^array._keys[[column name]]
1041: add_native_method("_keys", Method::CT_DYNAMIC, _keys, 0, 1);
1042:
1.5 moko 1043: // ^array._count[[all]]
1044: add_native_method("_count", Method::CT_DYNAMIC, _count, 0, 1);
1.1 moko 1045:
1.7 moko 1046: // ^array.for[index;value]{code}[delim]
1047: add_native_method("for", Method::CT_DYNAMIC, _for, 3, 3+1);
1.2 moko 1048: // ^array.foreach[index;value]{code}[delim]
1.7 moko 1049: add_native_method("foreach", Method::CT_DYNAMIC, _foreach, 3, 3+1);
1.1 moko 1050:
1.2 moko 1051: // ^array.sort[index;value]{string-key-maker}[[asc|desc]]
1052: // ^array.sort[index;value](numeric-key-maker)[[asc|desc]]
1.1 moko 1053: add_native_method("sort", Method::CT_DYNAMIC, _sort, 3, 4);
1054:
1.2 moko 1055: // ^array.select[index;value](bool-condition)[options hash]
1.1 moko 1056: add_native_method("select", Method::CT_DYNAMIC, _select, 3, 4);
1057:
1058: // ^array.reverse[]
1059: add_native_method("reverse", Method::CT_DYNAMIC, _reverse, 0, 0);
1060:
1.2 moko 1061: // ^array._at[first|last[;'key'|'value'|'hash']]
1062: // ^array._at([-+]offset)[['key'|'value'|'hash']]
1.1 moko 1063: add_native_method("_at", Method::CT_DYNAMIC, _at, 1, 2);
1064:
1065: #ifdef FEATURE_GET_ELEMENT4CALL
1066: // aliases without "_"
1067: add_native_method("keys", Method::CT_DYNAMIC, _keys, 0, 1);
1068: add_native_method("count", Method::CT_DYNAMIC, _count, 0, 0);
1069: add_native_method("at", Method::CT_DYNAMIC, _at, 1, 2);
1070: #endif
1071:
1072: }