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