1 /**
2  *     Copyright 2016-2017 Couchbase, Inc.
3  *
4  *   Licensed under the Apache License, Version 2.0 (the "License");
5  *   you may not use this file except in compliance with the License.
6  *   You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *   Unless required by applicable law or agreed to in writing, software
11  *   distributed under the License is distributed on an "AS IS" BASIS,
12  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *   See the License for the specific language governing permissions and
14  *   limitations under the License.
15  */
16 
17 #include "couchbase.h"
18 
19 #define LOGARGS(instance, lvl) LCB_LOG_##lvl, instance, "pcbc/n1ql", __FILE__, __LINE__
20 
21 typedef struct {
22     opcookie_res header;
23     lcb_U16 rflags;
24     PCBC_ZVAL row;
25 } opcookie_n1qlrow_res;
26 
n1qlrow_callback(lcb_t instance,int ignoreme,const lcb_RESPN1QL * resp)27 static void n1qlrow_callback(lcb_t instance, int ignoreme, const lcb_RESPN1QL *resp)
28 {
29     opcookie_n1qlrow_res *result = ecalloc(1, sizeof(opcookie_n1qlrow_res));
30     opcookie *cookie = (opcookie *)resp->cookie;
31     TSRMLS_FETCH();
32 
33     result->header.err = resp->rc;
34     result->rflags = resp->rflags;
35     PCBC_ZVAL_ALLOC(result->row);
36     ZVAL_NULL(PCBC_P(result->row));
37     if (cookie->json_response) {
38         int last_error;
39         int json_options = cookie->json_options;
40 
41         if (resp->rflags & LCB_RESP_F_FINAL) {
42             // parse meta into arrays
43             json_options |= PHP_JSON_OBJECT_AS_ARRAY;
44         }
45         PCBC_JSON_COPY_DECODE(PCBC_P(result->row), resp->row, resp->nrow, json_options, last_error);
46         if (last_error != 0) {
47             pcbc_log(LOGARGS(instance, WARN), "Failed to decode N1QL row as JSON: json_last_error=%d", last_error);
48             PCBC_STRINGL(result->row, resp->row, resp->nrow);
49         }
50     } else {
51         PCBC_STRINGL(result->row, resp->row, resp->nrow);
52     }
53     if (result->header.err != LCB_SUCCESS) {
54         int reported = 0;
55         if (Z_TYPE_P(PCBC_P(result->row)) == IS_ARRAY) {
56             zval *val;
57             val = php_array_fetch(PCBC_P(result->row), "errors");
58             if (val) {
59                 zval *err = php_array_fetch(val, "0");
60                 if (err) {
61                     char *msg = NULL;
62                     int msg_len;
63                     zend_bool need_free = 0;
64                     long code = php_array_fetch_long(err, "code");
65                     msg = php_array_fetch_string(err, "msg", &msg_len, &need_free);
66                     if (code && msg) {
67                         char *m = NULL;
68                         spprintf(&m, 0, "Failed to perform N1QL query. HTTP %d: code: %d, message: \"%*s\"",
69                                  (int)resp->htresp->htstatus, (int)code, msg_len, msg);
70                         PCBC_ZVAL_ALLOC(cookie->exc);
71                         pcbc_exception_init(PCBC_P(cookie->exc), code, m TSRMLS_CC);
72                         reported = 1;
73                         if (m) {
74                             efree(m);
75                         }
76                     }
77                     if (msg && need_free) {
78                         efree(msg);
79                     }
80                 }
81             }
82         }
83         if (!reported) {
84             if (resp->htresp) {
85                 pcbc_log(LOGARGS(instance, ERROR), "Failed to perform N1QL query. %d: %.*s", (int)resp->htresp->htstatus,
86                          (int)resp->nrow, (char *)resp->row);
87             } else {
88                 pcbc_log(LOGARGS(instance, ERROR), "Failed to perform N1QL query. %.*s",
89                          (int)resp->nrow, (char *)resp->row);
90             }
91         }
92     }
93 
94     opcookie_push((opcookie *)resp->cookie, &result->header);
95 }
96 
proc_n1qlrow_results(zval * return_value,opcookie * cookie TSRMLS_DC)97 static lcb_error_t proc_n1qlrow_results(zval *return_value, opcookie *cookie TSRMLS_DC)
98 {
99     opcookie_n1qlrow_res *res;
100     lcb_error_t err = LCB_SUCCESS;
101 
102     // Any error should cause everything to fail... for now?
103     err = opcookie_get_first_error(cookie);
104 
105     if (err == LCB_SUCCESS) {
106         PCBC_ZVAL rows;
107 
108         PCBC_ZVAL_ALLOC(rows);
109         array_init(PCBC_P(rows));
110 
111         object_init(return_value);
112         add_property_zval(return_value, "rows", PCBC_P(rows));
113         Z_DELREF_P(PCBC_P(rows));
114 
115         FOREACH_OPCOOKIE_RES(opcookie_n1qlrow_res, res, cookie)
116         {
117             if (res->rflags & LCB_RESP_F_FINAL) {
118                 zval *val;
119                 val = php_array_fetch(PCBC_P(res->row), "requestID");
120                 if (val) {
121                     add_property_zval(return_value, "requestId", val);
122                 }
123                 val = php_array_fetch(PCBC_P(res->row), "status");
124                 if (val) {
125                     add_property_zval(return_value, "status", val);
126                 }
127                 val = php_array_fetch(PCBC_P(res->row), "signature");
128                 if (val) {
129                     add_property_zval(return_value, "signature", val);
130                 }
131                 val = php_array_fetch(PCBC_P(res->row), "metrics");
132                 if (val) {
133                     add_property_zval(return_value, "metrics", val);
134                 }
135             } else {
136                 add_next_index_zval(PCBC_P(rows), PCBC_P(res->row));
137                 PCBC_ADDREF_P(PCBC_P(res->row));
138             }
139         }
140     }
141 
142     FOREACH_OPCOOKIE_RES(opcookie_n1qlrow_res, res, cookie)
143     {
144         zval_ptr_dtor(&res->row);
145     }
146 
147     return err;
148 }
149 
pcbc_bucket_n1ql_request(pcbc_bucket_t * bucket,lcb_CMDN1QL * cmd,int json_response,int json_options,int is_cbas,zval * return_value TSRMLS_DC)150 void pcbc_bucket_n1ql_request(pcbc_bucket_t *bucket, lcb_CMDN1QL *cmd, int json_response, int json_options, int is_cbas,
151                               zval *return_value TSRMLS_DC)
152 {
153     opcookie *cookie;
154     lcb_error_t err;
155 #ifdef LCB_TRACING
156     lcbtrace_TRACER *tracer = NULL;
157     lcb_N1QLHANDLE handle = NULL;
158 #endif
159 
160     cmd->callback = n1qlrow_callback;
161     cmd->content_type = PCBC_CONTENT_TYPE_JSON;
162     cookie = opcookie_init();
163     cookie->json_response = json_response;
164     cookie->json_options = json_options;
165     cookie->is_cbas = is_cbas;
166 #ifdef LCB_TRACING
167     tracer = lcb_get_tracer(bucket->conn->lcb);
168     if (tracer) {
169         cookie->span = lcbtrace_span_start(tracer, is_cbas ? "php/analytics" : "php/n1ql", 0, NULL);
170         lcbtrace_span_add_tag_str(cookie->span, LCBTRACE_TAG_COMPONENT, pcbc_client_string);
171         lcbtrace_span_add_tag_str(cookie->span, LCBTRACE_TAG_SERVICE,
172                                   is_cbas ? LCBTRACE_TAG_SERVICE_ANALYTICS : LCBTRACE_TAG_SERVICE_N1QL);
173         cmd->handle = &handle;
174     }
175 #endif
176     err = lcb_n1ql_query(bucket->conn->lcb, cookie, cmd);
177     if (err == LCB_SUCCESS) {
178 #ifdef LCB_TRACING
179         if (cookie->span) {
180             lcb_n1ql_set_parent_span(bucket->conn->lcb, handle, cookie->span);
181         }
182 #endif
183         lcb_wait(bucket->conn->lcb);
184         err = proc_n1qlrow_results(return_value, cookie TSRMLS_CC);
185     }
186     if (err != LCB_SUCCESS) {
187         if (Z_ISUNDEF(cookie->exc)) {
188             throw_lcb_exception(err);
189         } else {
190             zend_throw_exception_object(PCBC_P(cookie->exc) TSRMLS_CC);
191         }
192     }
193 #ifdef LCB_TRACING
194     if (cookie->span) {
195         lcbtrace_span_finish(cookie->span, LCBTRACE_NOW);
196     }
197 #endif
198     opcookie_destroy(cookie);
199 }
200