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