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