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