Merge lp:~hingo/drizzle/drizzle-json_server-keyvalue into lp:drizzle
- drizzle-json_server-keyvalue
- Merge into 7.2
Status: | Merged |
---|---|
Merged at revision: | 2557 |
Proposed branch: | lp:~hingo/drizzle/drizzle-json_server-keyvalue |
Merge into: | lp:drizzle |
Diff against target: |
1576 lines (+1147/-82) 10 files modified
drizzled/plugin/client/cached.h (+7/-4) drizzled/sql/result_set.h (+41/-11) drizzled/sql/result_set_meta_data.h (+24/-7) m4/pandora_have_libevent.m4 (+13/-0) plugin/json_server/docs/index.rst (+373/-11) plugin/json_server/json/json_value.cpp (+41/-4) plugin/json_server/json/value.h (+2/-1) plugin/json_server/json_server.cc (+637/-43) plugin/json_server/plugin.ac (+8/-0) plugin/json_server/plugin.ini (+1/-1) |
To merge this branch: | bzr merge lp:~hingo/drizzle/drizzle-json_server-keyvalue |
Related bugs: | |
Related blueprints: |
json server
(Medium)
json-server-refactoring
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Henrik Ingo | Needs Resubmitting | ||
Review via email: mp+105792@code.launchpad.net |
Commit message
Description of the change
This is 0.2 of json_server plugin. It adds a "pure json" key value api at the uri /json/. (The previous sql-over-http api is at URI /sql/.)
The new functionality still lacks tests, and all of this lacks documentation. We are already working on both, however it would be good to merge this now, because it is easier for us to work against trunk. We will shortly follow up with more tests and docs.
Brian Aker (brianaker) wrote : | # |
Henrik Ingo (hingo) wrote : | # |
Right. json_server now requires version 2.0 of libevent library. I've now pushed an addition to pandora_
Also pushed the documentation that we finished on Friday.
Preview Diff
1 | === modified file 'drizzled/plugin/client/cached.h' | |||
2 | --- drizzled/plugin/client/cached.h 2011-12-23 09:24:47 +0000 | |||
3 | +++ drizzled/plugin/client/cached.h 2012-05-20 15:31:19 +0000 | |||
4 | @@ -47,17 +47,20 @@ | |||
5 | 47 | virtual void sendFields(List<Item>& list) | 47 | virtual void sendFields(List<Item>& list) |
6 | 48 | { | 48 | { |
7 | 49 | List<Item>::iterator it(list.begin()); | 49 | List<Item>::iterator it(list.begin()); |
8 | 50 | |||
9 | 51 | column= 0; | 50 | column= 0; |
10 | 52 | max_column= 0; | 51 | max_column= 0; |
11 | 53 | 52 | ||
12 | 54 | while (Item* item= it++) | 53 | while (Item* item= it++) |
13 | 55 | { | 54 | { |
15 | 56 | SendField field; | 55 | // max_column starts from 0, ColumnCount from 1 |
16 | 56 | _result_set->setColumnCount(max_column+1); | ||
17 | 57 | // Get pointer to next column metadata class | ||
18 | 58 | SendField field = _result_set->getColumnInfo(max_column); | ||
19 | 57 | item->make_field(&field); | 59 | item->make_field(&field); |
20 | 60 | // Is this necessary or does it just set the same pointer back? | ||
21 | 61 | _result_set->setColumnInfo(max_column, field); | ||
22 | 58 | max_column++; | 62 | max_column++; |
23 | 59 | } | 63 | } |
24 | 60 | _result_set->setColumnCount(max_column); | ||
25 | 61 | // Moved to checkRowBegin() | 64 | // Moved to checkRowBegin() |
26 | 62 | //_result_set->createRow(); | 65 | //_result_set->createRow(); |
27 | 63 | } | 66 | } |
28 | @@ -75,7 +78,7 @@ | |||
29 | 75 | } | 78 | } |
30 | 76 | } | 79 | } |
31 | 77 | 80 | ||
33 | 78 | virtual void checkRowEnd() | 81 | virtual void checkRowEnd() |
34 | 79 | { | 82 | { |
35 | 80 | column++; | 83 | column++; |
36 | 81 | } | 84 | } |
37 | 82 | 85 | ||
38 | === modified file 'drizzled/sql/result_set.h' | |||
39 | --- drizzled/sql/result_set.h 2011-04-21 05:05:53 +0000 | |||
40 | +++ drizzled/sql/result_set.h 2012-05-20 15:31:19 +0000 | |||
41 | @@ -34,6 +34,7 @@ | |||
42 | 34 | #include <drizzled/visibility.h> | 34 | #include <drizzled/visibility.h> |
43 | 35 | #include <drizzled/sql/exception.h> | 35 | #include <drizzled/sql/exception.h> |
44 | 36 | #include <drizzled/sql/result_set_meta_data.h> | 36 | #include <drizzled/sql/result_set_meta_data.h> |
45 | 37 | #include <drizzled/field.h> | ||
46 | 37 | #include <cassert> | 38 | #include <cassert> |
47 | 38 | #include <queue> | 39 | #include <queue> |
48 | 39 | 40 | ||
49 | @@ -94,19 +95,48 @@ | |||
50 | 94 | bool error() const; | 95 | bool error() const; |
51 | 95 | sql::Exception getException() const; | 96 | sql::Exception getException() const; |
52 | 96 | 97 | ||
54 | 97 | ResultSet(size_t fields) : | 98 | ResultSet(size_t columns) : |
55 | 98 | _has_next_been_called(false), | 99 | _has_next_been_called(false), |
56 | 99 | _current_row(_results.end()), | 100 | _current_row(_results.end()), |
67 | 100 | _meta_data(fields) | 101 | _meta_data(columns) |
68 | 101 | { | 102 | { |
69 | 102 | } | 103 | } |
70 | 103 | 104 | ||
71 | 104 | void setColumnCount(size_t fields) | 105 | void setColumnCount(size_t columns) |
72 | 105 | { | 106 | { |
73 | 106 | _meta_data.setColumnCount(fields); | 107 | _meta_data.setColumnCount(columns); |
74 | 107 | } | 108 | } |
75 | 108 | 109 | ||
76 | 109 | ~ResultSet(); | 110 | void setColumnInfo(size_t column_number, const SendField& field) |
77 | 111 | { | ||
78 | 112 | _meta_data.setColumnInfo(column_number, field); | ||
79 | 113 | } | ||
80 | 114 | |||
81 | 115 | /** | ||
82 | 116 | * @brief Get object that holds column meta data. | ||
83 | 117 | * | ||
84 | 118 | * The following info is available: | ||
85 | 119 | * | ||
86 | 120 | * metadata = rs.getColumnInfo(0); | ||
87 | 121 | * metadata.db_name; | ||
88 | 122 | * metadata.org_table_name; | ||
89 | 123 | * metadata.org_col_name; | ||
90 | 124 | * metadata.table_name; | ||
91 | 125 | * metadata.col_name; | ||
92 | 126 | * metadata.charsetnr; | ||
93 | 127 | * metadata.flags; | ||
94 | 128 | * metadata.type; | ||
95 | 129 | * metadata.length; | ||
96 | 130 | * metadata.decimals; | ||
97 | 131 | * | ||
98 | 132 | * @see drizzled/item.cc to see where these are set. | ||
99 | 133 | */ | ||
100 | 134 | SendField getColumnInfo(size_t column_number) | ||
101 | 135 | { | ||
102 | 136 | return _meta_data.getColumnInfo(column_number); | ||
103 | 137 | } | ||
104 | 138 | |||
105 | 139 | ~ResultSet(); | ||
106 | 110 | 140 | ||
107 | 111 | void createRow(); | 141 | void createRow(); |
108 | 112 | void setColumn(size_t column_number, const std::string &arg); | 142 | void setColumn(size_t column_number, const std::string &arg); |
109 | 113 | 143 | ||
110 | === modified file 'drizzled/sql/result_set_meta_data.h' | |||
111 | --- drizzled/sql/result_set_meta_data.h 2011-04-21 05:05:53 +0000 | |||
112 | +++ drizzled/sql/result_set_meta_data.h 2012-05-20 15:31:19 +0000 | |||
113 | @@ -33,10 +33,13 @@ | |||
114 | 33 | 33 | ||
115 | 34 | #include <cassert> | 34 | #include <cassert> |
116 | 35 | #include <queue> | 35 | #include <queue> |
117 | 36 | #include <vector> | ||
118 | 37 | #include <drizzled/field.h> | ||
119 | 36 | 38 | ||
120 | 37 | namespace drizzled { | 39 | namespace drizzled { |
121 | 38 | namespace sql { | 40 | namespace sql { |
122 | 39 | 41 | ||
123 | 42 | |||
124 | 40 | class ResultSetMetaData | 43 | class ResultSetMetaData |
125 | 41 | { | 44 | { |
126 | 42 | public: | 45 | public: |
127 | @@ -51,17 +54,31 @@ | |||
128 | 51 | ResultSetMetaData(size_t columns) : | 54 | ResultSetMetaData(size_t columns) : |
129 | 52 | _columns(columns) | 55 | _columns(columns) |
130 | 53 | { | 56 | { |
138 | 54 | } | 57 | _columnInfo.resize(columns); |
139 | 55 | 58 | } | |
140 | 56 | void setColumnCount(size_t fields) | 59 | |
141 | 57 | { | 60 | void setColumnCount(size_t columns) |
142 | 58 | _columns= fields; | 61 | { |
143 | 59 | } | 62 | _columns= columns; |
144 | 60 | 63 | _columnInfo.resize(columns); | |
145 | 64 | } | ||
146 | 65 | |||
147 | 66 | void setColumnInfo(size_t column_number, const SendField& field) | ||
148 | 67 | { | ||
149 | 68 | _columnInfo.at(column_number) = field; | ||
150 | 69 | } | ||
151 | 70 | |||
152 | 71 | SendField getColumnInfo(size_t column_number) | ||
153 | 72 | { | ||
154 | 73 | return _columnInfo.at(column_number); | ||
155 | 74 | } | ||
156 | 75 | |||
157 | 61 | private: // Member methods | 76 | private: // Member methods |
158 | 62 | 77 | ||
159 | 63 | private: // Member variables | 78 | private: // Member variables |
160 | 64 | size_t _columns; | 79 | size_t _columns; |
161 | 80 | typedef std::vector<SendField> ColumnInfoVector; | ||
162 | 81 | ColumnInfoVector _columnInfo; | ||
163 | 65 | }; | 82 | }; |
164 | 66 | 83 | ||
165 | 67 | std::ostream& operator<<(std::ostream& output, const ResultSetMetaData &result_set); | 84 | std::ostream& operator<<(std::ostream& output, const ResultSetMetaData &result_set); |
166 | 68 | 85 | ||
167 | === modified file 'm4/pandora_have_libevent.m4' | |||
168 | --- m4/pandora_have_libevent.m4 2011-03-07 16:42:18 +0000 | |||
169 | +++ m4/pandora_have_libevent.m4 2012-05-20 15:31:19 +0000 | |||
170 | @@ -64,3 +64,16 @@ | |||
171 | 64 | AC_DEFUN([PANDORA_REQUIRE_LIBEVENT],[ | 64 | AC_DEFUN([PANDORA_REQUIRE_LIBEVENT],[ |
172 | 65 | AC_REQUIRE([_PANDORA_REQUIRE_LIBEVENT]) | 65 | AC_REQUIRE([_PANDORA_REQUIRE_LIBEVENT]) |
173 | 66 | ]) | 66 | ]) |
174 | 67 | |||
175 | 68 | AC_DEFUN([PANDORA_LIBEVENT_RECENT],[ | ||
176 | 69 | dnl FIXME I really wanted to check for existence of EVHTTP_REQ_DELETE, | ||
177 | 70 | dnl but autoconf gods were not favorable to me, so the below is all I got | ||
178 | 71 | AC_CACHE_CHECK([if libevent is recent enough], | ||
179 | 72 | [pandora_cv_libevent_recent], | ||
180 | 73 | [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ | ||
181 | 74 | #include <event2/event.h> | ||
182 | 75 | #include <event2/http.h> | ||
183 | 76 | ]])], | ||
184 | 77 | [pandora_cv_libevent_recent=yes], | ||
185 | 78 | [pandora_cv_libevent_recent=no])]) | ||
186 | 79 | ]) | ||
187 | 67 | 80 | ||
188 | === modified file 'plugin/json_server/docs/index.rst' | |||
189 | --- plugin/json_server/docs/index.rst 2011-10-23 05:45:09 +0000 | |||
190 | +++ plugin/json_server/docs/index.rst 2012-05-20 15:31:19 +0000 | |||
191 | @@ -3,7 +3,12 @@ | |||
192 | 3 | JSON Server | 3 | JSON Server |
193 | 4 | =========== | 4 | =========== |
194 | 5 | 5 | ||
196 | 6 | JSON HTTP interface. | 6 | JSON Server implements a simple HTTP server that allows you to access your |
197 | 7 | Drizzle database with JSON based protocols. Currently two API's are supported: | ||
198 | 8 | a SQL-over-HTTP protocol allows you to execute any single statement SQL | ||
199 | 9 | transactions and a pure JSON protocol currently supports storing of JSON | ||
200 | 10 | documents as blobs in a key-value table. | ||
201 | 11 | |||
202 | 7 | 12 | ||
203 | 8 | .. _json_server_loading: | 13 | .. _json_server_loading: |
204 | 9 | 14 | ||
205 | @@ -51,30 +56,380 @@ | |||
206 | 51 | 56 | ||
207 | 52 | :Scope: Global | 57 | :Scope: Global |
208 | 53 | :Dynamic: No | 58 | :Dynamic: No |
209 | 54 | :Option: :option:`--json-server.port` | ||
210 | 55 | 59 | ||
211 | 56 | Port number to use for connection or 0 for default (port 8086) | 60 | Port number to use for connection or 0 for default (port 8086) |
212 | 57 | 61 | ||
219 | 58 | .. _json_server_examples: | 62 | .. _json_server_apis: |
220 | 59 | 63 | ||
221 | 60 | Examples | 64 | APIs |
222 | 61 | -------- | 65 | ---- |
223 | 62 | 66 | ||
224 | 63 | Sorry, there are no examples for this plugin. | 67 | JSON Server supports a few APIs that offer different functionalities. Each API |
225 | 68 | is accessed via it's own URI, and parameters can be given in the query string | ||
226 | 69 | or in the POST data. | ||
227 | 70 | |||
228 | 71 | The APIs are versioned, the version number is prepended to the API name. If | ||
229 | 72 | functionality is added or changed, it will not be available if an API is | ||
230 | 73 | accessed via an earlier version number. Finally, the latest version of each API | ||
231 | 74 | is also available from the root, without any version number. | ||
232 | 75 | |||
233 | 76 | As of this writing, the following APIs exist: | ||
234 | 77 | |||
235 | 78 | .. code-block:: none | ||
236 | 79 | |||
237 | 80 | /0.1/sql | ||
238 | 81 | /0.2/sql | ||
239 | 82 | /sql | ||
240 | 83 | |||
241 | 84 | Because the SQL API did not change between 0.1 and 0.2, all of the above URIs | ||
242 | 85 | are exactly the same. | ||
243 | 86 | |||
244 | 87 | .. code-block:: none | ||
245 | 88 | |||
246 | 89 | /0.2/json | ||
247 | 90 | /json | ||
248 | 91 | |||
249 | 92 | The pure JSON API did not exist in the 0.1 release, as you can see from above. | ||
250 | 93 | |||
251 | 94 | .. code-block:: none | ||
252 | 95 | |||
253 | 96 | /version | ||
254 | 97 | / | ||
255 | 98 | |||
256 | 99 | The ``/version`` URI will return the version of Drizzle (in a JSON document, of | ||
257 | 100 | course): | ||
258 | 101 | |||
259 | 102 | .. code-block:: none | ||
260 | 103 | |||
261 | 104 | $ curl http://localhost:8086/version | ||
262 | 105 | { | ||
263 | 106 | "version" : "7.1.31.2451-snapshot" | ||
264 | 107 | } | ||
265 | 108 | |||
266 | 109 | The root URI returns a simple HTML GUI that can be used to test both the SQL and | ||
267 | 110 | pure JSON APIs. Just point your browser to http://localhost:8086/ and try it! | ||
268 | 111 | |||
269 | 112 | .. _json_server_sql_api: | ||
270 | 113 | |||
271 | 114 | The SQL-over-HTTP API: /sql | ||
272 | 115 | --------------------------- | ||
273 | 116 | |||
274 | 117 | The first API in JSON Server is the SQL-over-HTTP API. It allows you to execute | ||
275 | 118 | almost any SQL and the result is returned as a 2 dimensional JSON array. | ||
276 | 119 | |||
277 | 120 | On the HTTP level this is a simple API. The method is always ``POST`` and the | ||
278 | 121 | functionality is determined by the SQL statement you send. | ||
279 | 122 | |||
280 | 123 | .. code-block:: none | ||
281 | 124 | |||
282 | 125 | POST /sql | ||
283 | 126 | |||
284 | 127 | SELECT * from test.foo; | ||
285 | 128 | |||
286 | 129 | Returns: | ||
287 | 130 | |||
288 | 131 | .. code-block:: none | ||
289 | 132 | |||
290 | 133 | { | ||
291 | 134 | "query" : "SELECT * from test.foo;\n", | ||
292 | 135 | "result_set" : [ | ||
293 | 136 | [ "1", "Hello Drizzle Day Audience!" ], | ||
294 | 137 | [ "2", "this text came in over http" ] | ||
295 | 138 | ], | ||
296 | 139 | "sqlstate" : "00000" | ||
297 | 140 | } | ||
298 | 141 | |||
299 | 142 | The above corresponds to the following query from a drizzle command line: | ||
300 | 143 | |||
301 | 144 | .. code-block:: mysql | ||
302 | 145 | |||
303 | 146 | drizzle> select * from test.foo; | ||
304 | 147 | |||
305 | 148 | +----+-----------------------------+ | ||
306 | 149 | | id | bar | | ||
307 | 150 | +====+=============================+ | ||
308 | 151 | | 1 | Hello Drizzle Day Audience! | | ||
309 | 152 | +----+-----------------------------+ | ||
310 | 153 | | 2 | this text came in over http | | ||
311 | 154 | +----+-----------------------------+ | ||
312 | 155 | |||
313 | 156 | |||
314 | 157 | .. _json_server_json_api: | ||
315 | 158 | |||
316 | 159 | Pure JSON key-value API: /json | ||
317 | 160 | ------------------------------ | ||
318 | 161 | |||
319 | 162 | The pure JSON key-value API is found at the URI ``/json``. It takes a rather | ||
320 | 163 | opposite approach than the ``/sql`` API. Queries are expressed as JSON query | ||
321 | 164 | documents, similar to what is found in Metabase, CouchDB or MongoDB. It is not | ||
322 | 165 | possible to use any SQL. | ||
323 | 166 | |||
324 | 167 | The purpose of the ``/json`` API is to use Drizzle as a key-value document | ||
325 | 168 | storage. This means that the table layout is determined by the JSON Server | ||
326 | 169 | module. Therefore, it is not possible for the user to access arbitrary | ||
327 | 170 | relational tables via the ``/json`` API, rather tables must adhere to the | ||
328 | 171 | format explained further below, and it must contain valid JSON documents in the | ||
329 | 172 | data columns. | ||
330 | 173 | |||
331 | 174 | If you post (insert) a document to a table that doesn't exist, it will be | ||
332 | 175 | automatically created. For this reason, a user mostly doesn't need to even | ||
333 | 176 | know the specific format of a JSON server table. | ||
334 | 177 | |||
335 | 178 | .. _json_server_json_parameters: | ||
336 | 179 | |||
337 | 180 | Parameters | ||
338 | 181 | ^^^^^^^^^^ | ||
339 | 182 | |||
340 | 183 | Following parameters can be passed in the URI query string: | ||
341 | 184 | |||
342 | 185 | .. _json_server_json_parameters_id: | ||
343 | 186 | |||
344 | 187 | ``_id`` | ||
345 | 188 | |||
346 | 189 | :Type: Unsigned integer | ||
347 | 190 | :Mandatory: No | ||
348 | 191 | :Default: | ||
349 | 192 | |||
350 | 193 | Optionally, a user may also specify the _id value which is requested. | ||
351 | 194 | Typically this is given in the JSON query document instead. If both are given | ||
352 | 195 | the _id value in the query document has precedence. | ||
353 | 196 | |||
354 | 197 | .. _json_server_json_parameters_query: | ||
355 | 198 | |||
356 | 199 | ``query`` | ||
357 | 200 | |||
358 | 201 | :Type: JSON query document | ||
359 | 202 | :Mandatory: No | ||
360 | 203 | :Default: | ||
361 | 204 | |||
362 | 205 | A JSON document, the so called *query document*. This document specifies | ||
363 | 206 | which records/documents to return from the database. Currently it is only | ||
364 | 207 | possible to query for a single value by the primary key, which is | ||
365 | 208 | called ``_id``. Any other fields in the query document will be ignored. | ||
366 | 209 | |||
367 | 210 | The query parameter is used for GET, PUT and DELETE where it is passed in | ||
368 | 211 | URL encoded form in the URI query string. For POST requests the query | ||
369 | 212 | document is passed as the POST data. (In that case only the query document | ||
370 | 213 | is passed, there is no ``query=`` part, in other words the data is never | ||
371 | 214 | encoded in www-form-urlencoded format.) | ||
372 | 215 | |||
373 | 216 | Example query document: | ||
374 | 217 | |||
375 | 218 | .. code-block:: none | ||
376 | 219 | |||
377 | 220 | { "_id" : 1 } | ||
378 | 221 | |||
379 | 222 | .. _json_server_json_parameters_schema: | ||
380 | 223 | |||
381 | 224 | ``schema`` | ||
382 | 225 | |||
383 | 226 | :Type: String | ||
384 | 227 | :Mandatory: No | ||
385 | 228 | :Default: test | ||
386 | 229 | |||
387 | 230 | Name of the schema which we are querying. The schema must exist. | ||
388 | 231 | |||
389 | 232 | .. _json_server_parameters_table: | ||
390 | 233 | |||
391 | 234 | ``table`` | ||
392 | 235 | |||
393 | 236 | :Type: String | ||
394 | 237 | :Mandatory: No | ||
395 | 238 | :Default: jsonkv | ||
396 | 239 | |||
397 | 240 | Name of the table which we are querying. For POST requests, if the table | ||
398 | 241 | doesn't exist, it will be automatically created. For other requests the | ||
399 | 242 | table must exist, otherwise an error is returned. | ||
400 | 243 | |||
401 | 244 | POSTing a document | ||
402 | 245 | ^^^^^^^^^^^^^^^^^^ | ||
403 | 246 | |||
404 | 247 | .. code-block:: none | ||
405 | 248 | |||
406 | 249 | POST /json?schema=test&table=people HTTP/1.1 | ||
407 | 250 | |||
408 | 251 | { | ||
409 | 252 | "_id" : 2, | ||
410 | 253 | "document" : { "firstname" : "Henrik", "lastname" : "Ingo", "age" : 35} | ||
411 | 254 | } | ||
412 | 255 | |||
413 | 256 | Returns: | ||
414 | 257 | |||
415 | 258 | .. code-block:: none | ||
416 | 259 | |||
417 | 260 | HTTP/1.1 200 OK | ||
418 | 261 | Content-Type: text/html | ||
419 | 262 | |||
420 | 263 | { | ||
421 | 264 | "query" : { | ||
422 | 265 | "_id" : 2, | ||
423 | 266 | "document" : { | ||
424 | 267 | "age" : 35, | ||
425 | 268 | "firstname" : "Henrik", | ||
426 | 269 | "lastname" : "Ingo" | ||
427 | 270 | } | ||
428 | 271 | }, | ||
429 | 272 | "sqlstate" : "00000" | ||
430 | 273 | } | ||
431 | 274 | |||
432 | 275 | |||
433 | 276 | (The use of Content-type: text/html is considered a bug and will be | ||
434 | 277 | fixed in a future version.) | ||
435 | 278 | |||
436 | 279 | Under the hood, this has inserted the following record into a table "jsontable": | ||
437 | 280 | |||
438 | 281 | .. code-block:: mysql | ||
439 | 282 | |||
440 | 283 | drizzle> select * from people where _id=2; | ||
441 | 284 | |||
442 | 285 | +-----+--------------------------+ | ||
443 | 286 | | _id | document | | ||
444 | 287 | +=====+==========================+ | ||
445 | 288 | | 2 |{ | | ||
446 | 289 | | |"age" : 35, | | ||
447 | 290 | | |"firstname" : "Henrik", | | ||
448 | 291 | | |"lastname" : "Ingo" | | ||
449 | 292 | | |} | | ||
450 | 293 | +-----+--------------------------+ | ||
451 | 294 | |||
452 | 295 | The ``_id`` field is always present. If it isn't specified, an auto_increment | ||
453 | 296 | value will be generated. If a record with the given ``_id`` already exists in | ||
454 | 297 | the table, the record will be updated (using REPLACE INTO). | ||
455 | 298 | |||
456 | 299 | In addition there are one or more columns of type TEXT. | ||
457 | 300 | The column name(s) corresponds to the top level key(s) that were specified in the | ||
458 | 301 | POSTed JSON document. You can use any name(s) for the top level key(s), but | ||
459 | 302 | the name ``document`` is commonly used as a generic name. The contents of such a | ||
460 | 303 | column is the value of the corresponding top level key and has to be valid JSON. | ||
461 | 304 | |||
462 | 305 | A table of this format is automatically created when the first document is | ||
463 | 306 | POSTed to the table. This means that the column names are defined from the top | ||
464 | 307 | level key(s) of that first document and future JSON documents must use the same | ||
465 | 308 | top level key(s). Below the top level key(s) the JSON document can be of any | ||
466 | 309 | arbitrary structure. A common practice is to always use ``_id`` and ``document`` | ||
467 | 310 | as the top level keys, and place the actual JSON document, which can be of | ||
468 | 311 | arbitrary structure, under the ``document`` key. | ||
469 | 312 | |||
470 | 313 | |||
471 | 314 | GET a document | ||
472 | 315 | ^^^^^^^^^^^^^^ | ||
473 | 316 | |||
474 | 317 | The equivalent of an SQL SELECT is HTTP GET. | ||
475 | 318 | |||
476 | 319 | Below we use the query document ``{"_id" : 1 }`` in URL encoded form: | ||
477 | 320 | |||
478 | 321 | .. code-block:: none | ||
479 | 322 | |||
480 | 323 | GET /json?schema=test&table=people&query=%7B%22_id%22%20%3A%201%7D%0A | ||
481 | 324 | |||
482 | 325 | Returns | ||
483 | 326 | |||
484 | 327 | .. code-block:: none | ||
485 | 328 | |||
486 | 329 | HTTP/1.0 200 OK | ||
487 | 330 | Content-Type: text/html | ||
488 | 331 | |||
489 | 332 | { | ||
490 | 333 | "query" : { | ||
491 | 334 | "_id" : 1 | ||
492 | 335 | }, | ||
493 | 336 | "result_set" : [ | ||
494 | 337 | { | ||
495 | 338 | "_id" : 1, | ||
496 | 339 | "document" : { | ||
497 | 340 | "age" : 21, | ||
498 | 341 | "firstname" : "Mohit", | ||
499 | 342 | "lastname" : "Srivastava" | ||
500 | 343 | } | ||
501 | 344 | } | ||
502 | 345 | ], | ||
503 | 346 | "sqlstate" : "00000" | ||
504 | 347 | } | ||
505 | 348 | |||
506 | 349 | It is also allowed to specify the ``_id`` as a URI query string parameter and | ||
507 | 350 | omit the query document: | ||
508 | 351 | |||
509 | 352 | .. code-block:: none | ||
510 | 353 | |||
511 | 354 | GET /json?schema=test&table=people&_id=1 | ||
512 | 355 | |||
513 | 356 | If both are specified, the query document takes precedence. | ||
514 | 357 | |||
515 | 358 | Finally, it is possible to issue a GET request to a table without specifying | ||
516 | 359 | neither the ``_id`` parameter or a query document. In this case all records of | ||
517 | 360 | the whole table is returned. | ||
518 | 361 | |||
519 | 362 | |||
520 | 363 | Updating a record | ||
521 | 364 | ^^^^^^^^^^^^^^^^^ | ||
522 | 365 | |||
523 | 366 | To update a record, POST new version of json document with same ``_id`` as an | ||
524 | 367 | already existing record. | ||
525 | 368 | |||
526 | 369 | (PUT is currently not supported, instead POST is used for both inserting and | ||
527 | 370 | updating.) | ||
528 | 371 | |||
529 | 372 | Deleting a record | ||
530 | 373 | ^^^^^^^^^^^^^^^^^ | ||
531 | 374 | |||
532 | 375 | Below we use the query document ``{"_id" : 1 }`` in URL encoded form: | ||
533 | 376 | |||
534 | 377 | .. code-block:: none | ||
535 | 378 | |||
536 | 379 | DELETE http://14.139.228.217:8086/json?schema=test&table=people&query=%7B%22_id%22%20%3A%201%7D | ||
537 | 380 | |||
538 | 381 | Returns: | ||
539 | 382 | |||
540 | 383 | .. code-block:: none | ||
541 | 384 | |||
542 | 385 | HTTP/1.0 200 OK | ||
543 | 386 | Content-Type: text/html | ||
544 | 387 | |||
545 | 388 | { | ||
546 | 389 | "query" : { | ||
547 | 390 | "_id" : 1 | ||
548 | 391 | }, | ||
549 | 392 | "sqlstate" : "00000" | ||
550 | 393 | } | ||
551 | 394 | |||
552 | 395 | It is also allowed to specify the ``_id`` as a URI query string parameter and | ||
553 | 396 | omit the query document: | ||
554 | 397 | |||
555 | 398 | .. code-block:: none | ||
556 | 399 | |||
557 | 400 | DELETE /json?schema=test&table=people&_id=1 | ||
558 | 401 | |||
559 | 402 | If both are specified, the query document takes precedence. | ||
560 | 403 | |||
561 | 404 | .. _json_server_limitations: | ||
562 | 405 | |||
563 | 406 | Limitations | ||
564 | 407 | ^^^^^^^^^^^ | ||
565 | 408 | |||
566 | 409 | The ``/sql`` and ``/json`` APIs are both feature complete, yet JSON Server is | ||
567 | 410 | still an experimental module. There are known crashes, the module is still | ||
568 | 411 | single threaded and there is no authentication... and that's just a start! | ||
569 | 412 | These limitations are being worked on. For a full list of the current state of | ||
570 | 413 | JSON Server, please follow | ||
571 | 414 | `this launchpad blueprint <https://blueprints.launchpad.net/drizzle/+spec/json-server>`_. | ||
572 | 415 | |||
573 | 416 | An inherent limitation is that each HTTP request is its own transaction. While | ||
574 | 417 | it would be possible to support maintaining a complex SQL transaction over the | ||
575 | 418 | span of multiple HTTP requests, we currently do not plan to support that. | ||
576 | 64 | 419 | ||
577 | 65 | .. _json_server_authors: | 420 | .. _json_server_authors: |
578 | 66 | 421 | ||
579 | 67 | Authors | 422 | Authors |
580 | 68 | ------- | 423 | ------- |
581 | 69 | 424 | ||
583 | 70 | Stewart Smith | 425 | Stewart Smith, Henrik Ingo, Mohit Srivastava |
584 | 71 | 426 | ||
585 | 72 | .. _json_server_version: | 427 | .. _json_server_version: |
586 | 73 | 428 | ||
587 | 74 | Version | 429 | Version |
588 | 75 | ------- | 430 | ------- |
589 | 76 | 431 | ||
591 | 77 | This documentation applies to **json_server 0.1**. | 432 | This documentation applies to **json_server 0.2**. |
592 | 78 | 433 | ||
593 | 79 | To see which version of the plugin a Drizzle server is running, execute: | 434 | To see which version of the plugin a Drizzle server is running, execute: |
594 | 80 | 435 | ||
595 | @@ -87,4 +442,11 @@ | |||
596 | 87 | 442 | ||
597 | 88 | v0.1 | 443 | v0.1 |
598 | 89 | ^^^^ | 444 | ^^^^ |
600 | 90 | * First release. | 445 | * /sql API |
601 | 446 | * Simple web based GUI at / | ||
602 | 447 | * /version API | ||
603 | 448 | |||
604 | 449 | v0.2 | ||
605 | 450 | ^^^^ | ||
606 | 451 | * /json API supporting pure JSON key-value operations (POST, GET, DELETE) | ||
607 | 452 | * Automatic creation of table on first post. | ||
608 | 91 | 453 | ||
609 | === modified file 'plugin/json_server/json/json_value.cpp' | |||
610 | --- plugin/json_server/json/json_value.cpp 2011-08-20 13:41:35 +0000 | |||
611 | +++ plugin/json_server/json/json_value.cpp 2012-05-20 15:31:19 +0000 | |||
612 | @@ -40,6 +40,7 @@ | |||
613 | 40 | #include <plugin/json_server/json/value.h> | 40 | #include <plugin/json_server/json/value.h> |
614 | 41 | #include <plugin/json_server/json/writer.h> | 41 | #include <plugin/json_server/json/writer.h> |
615 | 42 | 42 | ||
616 | 43 | #include <cstdio> | ||
617 | 43 | #include <cassert> | 44 | #include <cassert> |
618 | 44 | #include <cstring> | 45 | #include <cstring> |
619 | 45 | #include <iostream> | 46 | #include <iostream> |
620 | @@ -308,6 +309,7 @@ | |||
621 | 308 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 309 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
622 | 309 | , itemIsUsed_( 0 ) | 310 | , itemIsUsed_( 0 ) |
623 | 310 | #endif | 311 | #endif |
624 | 312 | , value_as_string_( 0 ) | ||
625 | 311 | { | 313 | { |
626 | 312 | switch ( type_arg ) | 314 | switch ( type_arg ) |
627 | 313 | { | 315 | { |
628 | @@ -351,6 +353,7 @@ | |||
629 | 351 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 353 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
630 | 352 | , itemIsUsed_( 0 ) | 354 | , itemIsUsed_( 0 ) |
631 | 353 | #endif | 355 | #endif |
632 | 356 | , value_as_string_( 0 ) | ||
633 | 354 | { | 357 | { |
634 | 355 | value_.int_ = value; | 358 | value_.int_ = value; |
635 | 356 | } | 359 | } |
636 | @@ -362,6 +365,7 @@ | |||
637 | 362 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 365 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
638 | 363 | , itemIsUsed_( 0 ) | 366 | , itemIsUsed_( 0 ) |
639 | 364 | #endif | 367 | #endif |
640 | 368 | , value_as_string_( 0 ) | ||
641 | 365 | { | 369 | { |
642 | 366 | value_.uint_ = value; | 370 | value_.uint_ = value; |
643 | 367 | } | 371 | } |
644 | @@ -372,6 +376,7 @@ | |||
645 | 372 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 376 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
646 | 373 | , itemIsUsed_( 0 ) | 377 | , itemIsUsed_( 0 ) |
647 | 374 | #endif | 378 | #endif |
648 | 379 | , value_as_string_( 0 ) | ||
649 | 375 | { | 380 | { |
650 | 376 | value_.real_ = value; | 381 | value_.real_ = value; |
651 | 377 | } | 382 | } |
652 | @@ -383,6 +388,7 @@ | |||
653 | 383 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 388 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
654 | 384 | , itemIsUsed_( 0 ) | 389 | , itemIsUsed_( 0 ) |
655 | 385 | #endif | 390 | #endif |
656 | 391 | , value_as_string_( 0 ) | ||
657 | 386 | { | 392 | { |
658 | 387 | value_.string_ = valueAllocator()->duplicateStringValue( value ); | 393 | value_.string_ = valueAllocator()->duplicateStringValue( value ); |
659 | 388 | } | 394 | } |
660 | @@ -396,6 +402,7 @@ | |||
661 | 396 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 402 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
662 | 397 | , itemIsUsed_( 0 ) | 403 | , itemIsUsed_( 0 ) |
663 | 398 | #endif | 404 | #endif |
664 | 405 | , value_as_string_( 0 ) | ||
665 | 399 | { | 406 | { |
666 | 400 | value_.string_ = valueAllocator()->duplicateStringValue( beginValue, | 407 | value_.string_ = valueAllocator()->duplicateStringValue( beginValue, |
667 | 401 | UInt(endValue - beginValue) ); | 408 | UInt(endValue - beginValue) ); |
668 | @@ -409,6 +416,7 @@ | |||
669 | 409 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 416 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
670 | 410 | , itemIsUsed_( 0 ) | 417 | , itemIsUsed_( 0 ) |
671 | 411 | #endif | 418 | #endif |
672 | 419 | , value_as_string_( 0 ) | ||
673 | 412 | { | 420 | { |
674 | 413 | value_.string_ = valueAllocator()->duplicateStringValue( value.c_str(), | 421 | value_.string_ = valueAllocator()->duplicateStringValue( value.c_str(), |
675 | 414 | (unsigned int)value.length() ); | 422 | (unsigned int)value.length() ); |
676 | @@ -422,6 +430,7 @@ | |||
677 | 422 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 430 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
678 | 423 | , itemIsUsed_( 0 ) | 431 | , itemIsUsed_( 0 ) |
679 | 424 | #endif | 432 | #endif |
680 | 433 | , value_as_string_( 0 ) | ||
681 | 425 | { | 434 | { |
682 | 426 | value_.string_ = const_cast<char *>( value.c_str() ); | 435 | value_.string_ = const_cast<char *>( value.c_str() ); |
683 | 427 | } | 436 | } |
684 | @@ -435,6 +444,7 @@ | |||
685 | 435 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 444 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
686 | 436 | , itemIsUsed_( 0 ) | 445 | , itemIsUsed_( 0 ) |
687 | 437 | #endif | 446 | #endif |
688 | 447 | , value_as_string_( 0 ) | ||
689 | 438 | { | 448 | { |
690 | 439 | value_.string_ = valueAllocator()->duplicateStringValue( value, value.length() ); | 449 | value_.string_ = valueAllocator()->duplicateStringValue( value, value.length() ); |
691 | 440 | } | 450 | } |
692 | @@ -446,6 +456,7 @@ | |||
693 | 446 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 456 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
694 | 447 | , itemIsUsed_( 0 ) | 457 | , itemIsUsed_( 0 ) |
695 | 448 | #endif | 458 | #endif |
696 | 459 | , value_as_string_( 0 ) | ||
697 | 449 | { | 460 | { |
698 | 450 | value_.bool_ = value; | 461 | value_.bool_ = value; |
699 | 451 | } | 462 | } |
700 | @@ -457,6 +468,7 @@ | |||
701 | 457 | # ifdef JSON_VALUE_USE_INTERNAL_MAP | 468 | # ifdef JSON_VALUE_USE_INTERNAL_MAP |
702 | 458 | , itemIsUsed_( 0 ) | 469 | , itemIsUsed_( 0 ) |
703 | 459 | #endif | 470 | #endif |
704 | 471 | , value_as_string_( 0 ) | ||
705 | 460 | { | 472 | { |
706 | 461 | switch ( type_ ) | 473 | switch ( type_ ) |
707 | 462 | { | 474 | { |
708 | @@ -504,7 +516,6 @@ | |||
709 | 504 | } | 516 | } |
710 | 505 | } | 517 | } |
711 | 506 | 518 | ||
712 | 507 | |||
713 | 508 | Value::~Value() | 519 | Value::~Value() |
714 | 509 | { | 520 | { |
715 | 510 | switch ( type_ ) | 521 | switch ( type_ ) |
716 | @@ -514,7 +525,9 @@ | |||
717 | 514 | case uintValue: | 525 | case uintValue: |
718 | 515 | case realValue: | 526 | case realValue: |
719 | 516 | case booleanValue: | 527 | case booleanValue: |
721 | 517 | break; | 528 | if ( value_as_string_ ) |
722 | 529 | valueAllocator()->releaseStringValue( value_as_string_ ); | ||
723 | 530 | break; | ||
724 | 518 | case stringValue: | 531 | case stringValue: |
725 | 519 | if ( allocated_ ) | 532 | if ( allocated_ ) |
726 | 520 | valueAllocator()->releaseStringValue( value_.string_ ); | 533 | valueAllocator()->releaseStringValue( value_.string_ ); |
727 | @@ -715,9 +728,12 @@ | |||
728 | 715 | return value_.string_; | 728 | return value_.string_; |
729 | 716 | } | 729 | } |
730 | 717 | 730 | ||
732 | 718 | 731 | /** | |
733 | 732 | * If type_ is not stringValue, we will here convert other values to value_as_string_ field, | ||
734 | 733 | * then return it. | ||
735 | 734 | */ | ||
736 | 719 | std::string | 735 | std::string |
738 | 720 | Value::asString() const | 736 | Value::asString() |
739 | 721 | { | 737 | { |
740 | 722 | switch ( type_ ) | 738 | switch ( type_ ) |
741 | 723 | { | 739 | { |
742 | @@ -728,8 +744,29 @@ | |||
743 | 728 | case booleanValue: | 744 | case booleanValue: |
744 | 729 | return value_.bool_ ? "true" : "false"; | 745 | return value_.bool_ ? "true" : "false"; |
745 | 730 | case intValue: | 746 | case intValue: |
746 | 747 | if(!value_as_string_) | ||
747 | 748 | { | ||
748 | 749 | char buf[64]; | ||
749 | 750 | sprintf(buf, "%d", value_.int_); | ||
750 | 751 | value_as_string_ = valueAllocator()->duplicateStringValue( buf ); | ||
751 | 752 | } | ||
752 | 753 | return value_as_string_; | ||
753 | 731 | case uintValue: | 754 | case uintValue: |
754 | 755 | if(!value_as_string_) | ||
755 | 756 | { | ||
756 | 757 | char buf[64]; | ||
757 | 758 | sprintf(buf, "%d", value_.uint_); | ||
758 | 759 | value_as_string_ = valueAllocator()->duplicateStringValue( buf ); | ||
759 | 760 | } | ||
760 | 761 | return value_as_string_; | ||
761 | 732 | case realValue: | 762 | case realValue: |
762 | 763 | if(!value_as_string_) | ||
763 | 764 | { | ||
764 | 765 | char buf[256]; | ||
765 | 766 | sprintf(buf, "%f", value_.real_); | ||
766 | 767 | value_as_string_ = valueAllocator()->duplicateStringValue( buf ); | ||
767 | 768 | } | ||
768 | 769 | return value_as_string_; | ||
769 | 733 | case arrayValue: | 770 | case arrayValue: |
770 | 734 | case objectValue: | 771 | case objectValue: |
771 | 735 | JSON_ASSERT_MESSAGE( false, "Type is not convertible to string" ); | 772 | JSON_ASSERT_MESSAGE( false, "Type is not convertible to string" ); |
772 | 736 | 773 | ||
773 | === modified file 'plugin/json_server/json/value.h' | |||
774 | --- plugin/json_server/json/value.h 2011-08-19 09:52:50 +0000 | |||
775 | +++ plugin/json_server/json/value.h 2012-05-20 15:31:19 +0000 | |||
776 | @@ -263,7 +263,7 @@ | |||
777 | 263 | int compare( const Value &other ); | 263 | int compare( const Value &other ); |
778 | 264 | 264 | ||
779 | 265 | const char *asCString() const; | 265 | const char *asCString() const; |
781 | 266 | std::string asString() const; | 266 | std::string asString(); |
782 | 267 | # ifdef JSON_USE_CPPTL | 267 | # ifdef JSON_USE_CPPTL |
783 | 268 | CppTL::ConstString asConstString() const; | 268 | CppTL::ConstString asConstString() const; |
784 | 269 | # endif | 269 | # endif |
785 | @@ -481,6 +481,7 @@ | |||
786 | 481 | int memberNameIsStatic_ : 1; // used by the ValueInternalMap container. | 481 | int memberNameIsStatic_ : 1; // used by the ValueInternalMap container. |
787 | 482 | # endif | 482 | # endif |
788 | 483 | CommentInfo *comments_; | 483 | CommentInfo *comments_; |
789 | 484 | char *value_as_string_; // Used when asString is called on non-string types. | ||
790 | 484 | }; | 485 | }; |
791 | 485 | 486 | ||
792 | 486 | 487 | ||
793 | 487 | 488 | ||
794 | === modified file 'plugin/json_server/json_server.cc' | |||
795 | --- plugin/json_server/json_server.cc 2012-01-16 02:37:54 +0000 | |||
796 | +++ plugin/json_server/json_server.cc 2012-05-20 15:31:19 +0000 | |||
797 | @@ -1,7 +1,7 @@ | |||
798 | 1 | /* - mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- | 1 | /* - mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- |
799 | 2 | * vim:expandtab:shiftwidth=2:tabstop=2:smarttab: | 2 | * vim:expandtab:shiftwidth=2:tabstop=2:smarttab: |
800 | 3 | * | 3 | * |
802 | 4 | * Copyright (C) 2011 Stewart Smith | 4 | * Copyright (C) 2011 Stewart Smith, Henrik Ingo, Mohit Srivastava |
803 | 5 | * | 5 | * |
804 | 6 | * This program is free software; you can redistribute it and/or modify | 6 | * This program is free software; you can redistribute it and/or modify |
805 | 7 | * it under the terms of the GNU General Public License as published by | 7 | * it under the terms of the GNU General Public License as published by |
806 | @@ -17,7 +17,21 @@ | |||
807 | 17 | * along with this program; if not, write to the Free Software | 17 | * along with this program; if not, write to the Free Software |
808 | 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
809 | 19 | */ | 19 | */ |
811 | 20 | 20 | /** | |
812 | 21 | * @file Implements an HTTP server that will parse JSON and SQL queries | ||
813 | 22 | * | ||
814 | 23 | * @todo Refactoring ideas: | ||
815 | 24 | * - Anything HTML should really be a separate file, not strings embedded | ||
816 | 25 | * in C++. | ||
817 | 26 | * - Put all json handling into try/catch blocks, the parser likes to throw | ||
818 | 27 | * exceptions which crash drizzled if not caught. | ||
819 | 28 | * - Need to implement a worker thread pool. Make workers proper OO classes. | ||
820 | 29 | * | ||
821 | 30 | * @todo Implement HTTP response codes other than just 200 as defined in | ||
822 | 31 | * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html | ||
823 | 32 | * | ||
824 | 33 | * @todo Shouldn't we be using event2/http.h? Why does this even work without it? | ||
825 | 34 | */ | ||
826 | 21 | 35 | ||
827 | 22 | #include <config.h> | 36 | #include <config.h> |
828 | 23 | 37 | ||
829 | @@ -76,7 +90,11 @@ | |||
830 | 76 | extern "C" void process_root_request(struct evhttp_request *req, void* ); | 90 | extern "C" void process_root_request(struct evhttp_request *req, void* ); |
831 | 77 | extern "C" void process_api01_version_req(struct evhttp_request *req, void* ); | 91 | extern "C" void process_api01_version_req(struct evhttp_request *req, void* ); |
832 | 78 | extern "C" void process_api01_sql_req(struct evhttp_request *req, void* ); | 92 | extern "C" void process_api01_sql_req(struct evhttp_request *req, void* ); |
834 | 79 | 93 | extern "C" void process_api02_json_req(struct evhttp_request *req, void* ); | |
835 | 94 | extern "C" void process_api02_json_get_req(struct evhttp_request *req, void* ); | ||
836 | 95 | extern "C" void process_api02_json_post_req(struct evhttp_request *req, void* ); | ||
837 | 96 | /* extern "C" void process_api02_json_put_req(struct evhttp_request *req, void* ); */ | ||
838 | 97 | extern "C" void process_api02_json_delete_req(struct evhttp_request *req, void* ); | ||
839 | 80 | extern "C" void process_request(struct evhttp_request *req, void* ) | 98 | extern "C" void process_request(struct evhttp_request *req, void* ) |
840 | 81 | { | 99 | { |
841 | 82 | struct evbuffer *buf = evbuffer_new(); | 100 | struct evbuffer *buf = evbuffer_new(); |
842 | @@ -92,40 +110,78 @@ | |||
843 | 92 | 110 | ||
844 | 93 | std::string output; | 111 | std::string output; |
845 | 94 | 112 | ||
865 | 95 | output.append("<html><head><title>JSON DATABASE interface demo</title></head>" | 113 | output.append("<html><head><title>JSON DATABASE interface demo</title></head>\n" |
866 | 96 | "<body>" | 114 | "<body>\n" |
867 | 97 | "<script lang=\"javascript\">" | 115 | "<script lang=\"javascript\">\n" |
868 | 98 | "function to_table(obj) {" | 116 | "function to_table(obj) {\n" |
869 | 99 | " var str = '<table>';" | 117 | " var str = '<table border=\"1\">';\n" |
870 | 100 | "for (var r=0; r< obj.length; r++) {" | 118 | "for (var r=0; r< obj.length; r++) {\n" |
871 | 101 | " str+='<tr>';" | 119 | " str+='<tr>';\n" |
872 | 102 | " for (var c=0; c < obj[r].length; c++) {" | 120 | " for (var c=0; c < obj[r].length; c++) {\n" |
873 | 103 | " str+= '<td>' + obj[r][c] + '</td>';" | 121 | " str+= '<td>' + obj[r][c] + '</td>';\n" |
874 | 104 | " }" | 122 | " }\n" |
875 | 105 | " str+='</tr>';" | 123 | " str+='</tr>';\n" |
876 | 106 | "}" | 124 | "}\n" |
877 | 107 | "str+='</table>';" | 125 | "str+='</table>';\n" |
878 | 108 | "return str;" | 126 | "return str;\n" |
879 | 109 | "}" | 127 | "}\n" |
880 | 110 | "function run_query()\n" | 128 | "function to_table_from_json(obj) {\n" |
881 | 111 | "{" | 129 | " var str = '<table border=\"1\">';\n" |
882 | 112 | "var url = document.getElementById(\"baseurl\").innerHTML;\n" | 130 | "for (var r=0; r< obj.length; r++) {\n" |
883 | 113 | "var query= document.getElementById(\"query\").value;\n" | 131 | " str+='<tr>';\n" |
884 | 132 | " str+='<td>' + obj[r]['_id'] + '</td>';\n" | ||
885 | 133 | " str+='<td>' + JSON.stringify(obj[r]['document']) + '</td>';\n" | ||
886 | 134 | " str+='</tr>';\n" | ||
887 | 135 | "}\n" | ||
888 | 136 | "str+='</table>';\n" | ||
889 | 137 | "return str;\n" | ||
890 | 138 | "}\n" | ||
891 | 139 | "function run_sql_query()\n" | ||
892 | 140 | "{\n" | ||
893 | 141 | "var url = window.location;\n" | ||
894 | 142 | "var query= document.getElementById(\"sql_query\").value;\n" | ||
895 | 114 | "var xmlHttp = new XMLHttpRequest();\n" | 143 | "var xmlHttp = new XMLHttpRequest();\n" |
896 | 115 | "xmlHttp.onreadystatechange = function () {\n" | 144 | "xmlHttp.onreadystatechange = function () {\n" |
897 | 145 | "document.getElementById(\"responseText\").value = xmlHttp.responseText;\n" | ||
898 | 116 | "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n" | 146 | "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n" |
899 | 117 | "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n" | 147 | "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n" |
900 | 118 | "document.getElementById( \"resultset\").innerHTML= to_table(info.result_set);\n" | 148 | "document.getElementById( \"resultset\").innerHTML= to_table(info.result_set);\n" |
901 | 119 | "}\n" | 149 | "}\n" |
902 | 120 | "};\n" | 150 | "};\n" |
906 | 121 | "xmlHttp.open(\"POST\", url + \"/0.1/sql\", true);" | 151 | "xmlHttp.open(\"POST\", url + \"sql\", true);\n" |
907 | 122 | "xmlHttp.send(query);" | 152 | "xmlHttp.send(query);\n" |
908 | 123 | "}" | 153 | "}\n" |
909 | 154 | "\n\n" | ||
910 | 155 | "function run_json_query()\n" | ||
911 | 156 | "{\n" | ||
912 | 157 | //"alert('run_json_query');" | ||
913 | 158 | "var url = window.location;\n" | ||
914 | 159 | "var method= document.getElementById(\"json_method\").value;\n" | ||
915 | 160 | "var query= document.getElementById(\"json_query\").value;\n" | ||
916 | 161 | "var schema= document.getElementById(\"schema\").value;\n" | ||
917 | 162 | "var table= document.getElementById(\"table\").value;\n" | ||
918 | 163 | "var xmlHttp = new XMLHttpRequest();\n" | ||
919 | 164 | "xmlHttp.onreadystatechange = function () {\n" | ||
920 | 165 | //"alert(xmlHttp.responseText);" | ||
921 | 166 | "document.getElementById(\"responseText\").value = xmlHttp.responseText;\n" | ||
922 | 167 | "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n" | ||
923 | 168 | "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n" | ||
924 | 169 | "document.getElementById( \"resultset\").innerHTML= to_table_from_json(info.result_set);\n" | ||
925 | 170 | "}\n" | ||
926 | 171 | "};\n" | ||
927 | 172 | "if( method == \"POST\" ) {\n" | ||
928 | 173 | "xmlHttp.open(method, url + \"json?schema=\" + schema + \"&table=\" + table, true);\n" | ||
929 | 174 | "xmlHttp.send(query);\n" | ||
930 | 175 | "} else {\n" | ||
931 | 176 | "xmlHttp.open(method, url + \"json?schema=\" + schema + \"&table=\" + table + \"&query=\" + encodeURIComponent(query), true);\n" | ||
932 | 177 | "xmlHttp.send();\n" | ||
933 | 178 | "}\n" | ||
934 | 179 | "}\n" | ||
935 | 124 | "\n\n" | 180 | "\n\n" |
936 | 125 | "function update_version()\n" | 181 | "function update_version()\n" |
940 | 126 | "{drizzle_version(document.getElementById(\"baseurl\").innerHTML);}\n\n" | 182 | "{drizzle_version(window.location);}\n\n" |
941 | 127 | "function drizzle_version($url)" | 183 | "function drizzle_version($url)\n" |
942 | 128 | "{" | 184 | "{\n" |
943 | 129 | "var xmlHttp = new XMLHttpRequest();\n" | 185 | "var xmlHttp = new XMLHttpRequest();\n" |
944 | 130 | "xmlHttp.onreadystatechange = function () {\n" | 186 | "xmlHttp.onreadystatechange = function () {\n" |
945 | 131 | "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n" | 187 | "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n" |
946 | @@ -133,19 +189,32 @@ | |||
947 | 133 | "document.getElementById( \"drizzleversion\").innerHTML= info.version;\n" | 189 | "document.getElementById( \"drizzleversion\").innerHTML= info.version;\n" |
948 | 134 | "}\n" | 190 | "}\n" |
949 | 135 | "};\n" | 191 | "};\n" |
963 | 136 | "xmlHttp.open(\"GET\", $url + \"/0.1/version\", true);" | 192 | "xmlHttp.open(\"GET\", $url + \"version\", true);\n" |
964 | 137 | "xmlHttp.send(null);" | 193 | "xmlHttp.send(null);\n" |
965 | 138 | "}" | 194 | "}\n" |
966 | 139 | "</script>" | 195 | "</script>\n" |
967 | 140 | "<p>Drizzle Server at: <a id=\"baseurl\">http://localhost:8765</a></p>" | 196 | "<p>Drizzle server version: <a id=\"drizzleversion\"></a></p>\n" |
968 | 141 | "<p>Drizzle server version: <a id=\"drizzleversion\"></a></p>" | 197 | "<p><textarea rows=\"3\" cols=\"80\" id=\"sql_query\">\n" |
969 | 142 | "<p><textarea rows=\"3\" cols=\"40\" id=\"query\">" | 198 | "SELECT * from DATA_DICTIONARY.GLOBAL_STATUS;\n" |
970 | 143 | "SELECT * from DATA_DICTIONARY.GLOBAL_STATUS;" | 199 | "</textarea>\n" |
971 | 144 | "</textarea>" | 200 | "<button type=\"button\" onclick=\"run_sql_query();\">Execute SQL Query</button>\n" |
972 | 145 | "<button type=\"button\" onclick=\"run_query();\">Execute Query</button>" | 201 | "</p><p>\n" |
973 | 146 | "<div id=\"resultset\"/>" | 202 | "<textarea rows=\"8\" cols=\"80\" id=\"json_query\">\n" |
974 | 147 | "<script lang=\"javascript\">update_version(); run_query();</script>" | 203 | "{\"_id\" : 1}\n" |
975 | 148 | "</body></html>"); | 204 | "</textarea>\n" |
976 | 205 | "<button type=\"button\" onclick=\"run_json_query();\">Execute JSON Query</button>\n" | ||
977 | 206 | "<br />\n" | ||
978 | 207 | "<select id=\"json_method\"><option value=\"GET\">GET</option>" | ||
979 | 208 | "<option value=\"POST\">POST</option>" | ||
980 | 209 | "<option value=\"PUT\">PUT</option>" | ||
981 | 210 | "<option value=\"DELETE\">DELETE</option></select>" | ||
982 | 211 | "<script lang=\"javascript\">document.write(window.location);</script>json?schema=\n" | ||
983 | 212 | "<input type=\"text\" id=\"schema\" value=\"test\"/>" | ||
984 | 213 | "&table=<input type=\"text\" id=\"table\" value=\"jsonkv\"/>\n" | ||
985 | 214 | "</p><hr />\n<div id=\"resultset\"></div>\n" | ||
986 | 215 | "<hr /><p><textarea rows=\"12\" cols=\"80\" id=\"responseText\" ></textarea></p>" | ||
987 | 216 | "<script lang=\"javascript\">update_version(); run_sql_query();</script>\n" | ||
988 | 217 | "</body></html>\n"); | ||
989 | 149 | 218 | ||
990 | 150 | evbuffer_add(buf, output.c_str(), output.length()); | 219 | evbuffer_add(buf, output.c_str(), output.length()); |
991 | 151 | evhttp_send_reply(req, HTTP_OK, "OK", buf); | 220 | evhttp_send_reply(req, HTTP_OK, "OK", buf); |
992 | @@ -203,6 +272,7 @@ | |||
993 | 203 | { | 272 | { |
994 | 204 | root["error_message"]= exception.getErrorMessage(); | 273 | root["error_message"]= exception.getErrorMessage(); |
995 | 205 | root["error_code"]= exception.getErrorCode(); | 274 | root["error_code"]= exception.getErrorCode(); |
996 | 275 | root["schema"]= "test"; | ||
997 | 206 | } | 276 | } |
998 | 207 | 277 | ||
999 | 208 | while (result_set.next()) | 278 | while (result_set.next()) |
1000 | @@ -227,6 +297,515 @@ | |||
1001 | 227 | evhttp_send_reply(req, HTTP_OK, "OK", buf); | 297 | evhttp_send_reply(req, HTTP_OK, "OK", buf); |
1002 | 228 | } | 298 | } |
1003 | 229 | 299 | ||
1004 | 300 | extern "C" void process_api02_json_req(struct evhttp_request *req, void* ) | ||
1005 | 301 | { | ||
1006 | 302 | if( req->type == EVHTTP_REQ_GET ) | ||
1007 | 303 | { | ||
1008 | 304 | process_api02_json_get_req( req, NULL); | ||
1009 | 305 | // } elseif ( req->type == EVHTTP_REQ_PUT ) { | ||
1010 | 306 | //process_api02_json_put_req( req, NULL); | ||
1011 | 307 | } else if ( req->type == EVHTTP_REQ_POST ) { | ||
1012 | 308 | process_api02_json_post_req( req, NULL); | ||
1013 | 309 | } else if ( req->type == EVHTTP_REQ_DELETE ) { | ||
1014 | 310 | process_api02_json_delete_req( req, NULL); | ||
1015 | 311 | } | ||
1016 | 312 | } | ||
1017 | 313 | |||
1018 | 314 | /** | ||
1019 | 315 | * Transform a HTTP GET to SELECT and return results based on input json document | ||
1020 | 316 | * | ||
1021 | 317 | * @todo allow DBA to set default schema (also in post,del methods) | ||
1022 | 318 | * @todo allow DBA to set whether to use strict mode for parsing json (should get rid of white space), especially for POST of course. | ||
1023 | 319 | * | ||
1024 | 320 | * @param req should contain a "table" parameter in request uri. "query", "_id" and "schema" are optional. | ||
1025 | 321 | * @return a json document is returned to client with evhttp_send_reply() | ||
1026 | 322 | */ | ||
1027 | 323 | void process_api02_json_get_req(struct evhttp_request *req, void* ) | ||
1028 | 324 | { | ||
1029 | 325 | int http_response_code = HTTP_OK; | ||
1030 | 326 | const char *http_response_text; | ||
1031 | 327 | http_response_text = "OK"; | ||
1032 | 328 | |||
1033 | 329 | struct evbuffer *buf = evbuffer_new(); | ||
1034 | 330 | if (buf == NULL) return; | ||
1035 | 331 | |||
1036 | 332 | Json::Value json_out; | ||
1037 | 333 | |||
1038 | 334 | std::string input; | ||
1039 | 335 | // Schema and table are given in request uri. | ||
1040 | 336 | // TODO: If we want to be really NoSQL, we will some day allow to use synonyms like "collection" instead of "table". | ||
1041 | 337 | // For GET, also the query is in the uri | ||
1042 | 338 | const char *schema; | ||
1043 | 339 | const char *table; | ||
1044 | 340 | const char *query; | ||
1045 | 341 | const char *id; | ||
1046 | 342 | evhttp_parse_query(evhttp_request_uri(req), req->input_headers); | ||
1047 | 343 | schema = (char *)evhttp_find_header(req->input_headers, "schema"); | ||
1048 | 344 | table = (char *)evhttp_find_header(req->input_headers, "table"); | ||
1049 | 345 | query = (char *)evhttp_find_header(req->input_headers, "query"); | ||
1050 | 346 | id = (char *)evhttp_find_header(req->input_headers, "_id"); | ||
1051 | 347 | |||
1052 | 348 | // query can be null if _id was given | ||
1053 | 349 | if ( query == NULL || strcmp(query, "") == 0 ) | ||
1054 | 350 | { | ||
1055 | 351 | // Empty JSON object | ||
1056 | 352 | query = "{}"; | ||
1057 | 353 | } | ||
1058 | 354 | input.append(query, strlen(query)); | ||
1059 | 355 | |||
1060 | 356 | // Set test as default schema | ||
1061 | 357 | if ( !strcmp( schema, "") || schema == NULL) | ||
1062 | 358 | { | ||
1063 | 359 | schema = "test"; | ||
1064 | 360 | } | ||
1065 | 361 | |||
1066 | 362 | // Parse "input" into "json_in". | ||
1067 | 363 | Json::Value json_in; | ||
1068 | 364 | Json::Features json_conf; | ||
1069 | 365 | Json::Reader reader(json_conf); | ||
1070 | 366 | bool retval = reader.parse(input, json_in); | ||
1071 | 367 | if (retval != true) { | ||
1072 | 368 | json_out["error_type"]="json error"; | ||
1073 | 369 | json_out["error_message"]= reader.getFormatedErrorMessages(); | ||
1074 | 370 | } | ||
1075 | 371 | else if (strcmp( table, "") == 0 || table == NULL) { | ||
1076 | 372 | json_out["error_type"]="http error"; | ||
1077 | 373 | json_out["error_message"]= "You must specify \"table\" in the request uri query string."; | ||
1078 | 374 | http_response_code = HTTP_NOTFOUND; | ||
1079 | 375 | http_response_text = "You must specify \"table\" in the request uri query string."; | ||
1080 | 376 | } | ||
1081 | 377 | else { | ||
1082 | 378 | // It is allowed to specify _id in the uri and leave it out from the json query. | ||
1083 | 379 | // In that case we put the value from uri into json_in here. | ||
1084 | 380 | // If both are specified, the one existing in json_in wins. (This is still valid, no error.) | ||
1085 | 381 | if ( ! json_in["_id"].asBool() ) | ||
1086 | 382 | { | ||
1087 | 383 | if( id ) { | ||
1088 | 384 | json_in["_id"] = (Json::Value::UInt) atol(id); | ||
1089 | 385 | } | ||
1090 | 386 | } | ||
1091 | 387 | |||
1092 | 388 | // TODO: In a later stage we'll allow the situation where _id isn't given but some other column for where. | ||
1093 | 389 | // TODO: Need to do json_in[].type() first and juggle it from there to be safe. See json/value.h | ||
1094 | 390 | // TODO: Don't SELECT * but only fields given in json query document | ||
1095 | 391 | char sqlformat[1024];; | ||
1096 | 392 | char buffer[1024]; | ||
1097 | 393 | if ( json_in["_id"].asBool() ) | ||
1098 | 394 | { | ||
1099 | 395 | // Now we build an SQL query, using _id from json_in | ||
1100 | 396 | sprintf(sqlformat, "%s", "SELECT * FROM `%s`.`%s` WHERE _id=%i;"); | ||
1101 | 397 | sprintf(buffer, sqlformat, schema, table, json_in["_id"].asInt()); | ||
1102 | 398 | } | ||
1103 | 399 | else { | ||
1104 | 400 | // If neither _id nor query are given, we return the full table. (No error, maybe this is what you really want? Blame yourself.) | ||
1105 | 401 | sprintf(sqlformat, "%s", "SELECT * FROM `%s`.`%s`;"); | ||
1106 | 402 | sprintf(buffer, sqlformat, schema, table); | ||
1107 | 403 | } | ||
1108 | 404 | |||
1109 | 405 | std::string sql = ""; | ||
1110 | 406 | sql.append(buffer, strlen(buffer)); | ||
1111 | 407 | |||
1112 | 408 | // We have sql string. Use Execute API to run it and convert results back to JSON. | ||
1113 | 409 | drizzled::Session::shared_ptr _session= drizzled::Session::make_shared(drizzled::plugin::Listen::getNullClient(), | ||
1114 | 410 | drizzled::catalog::local()); | ||
1115 | 411 | drizzled::identifier::user::mptr user_id= identifier::User::make_shared(); | ||
1116 | 412 | user_id->setUser(""); | ||
1117 | 413 | _session->setUser(user_id); | ||
1118 | 414 | //_session->set_schema("test"); | ||
1119 | 415 | |||
1120 | 416 | drizzled::Execute execute(*(_session.get()), true); | ||
1121 | 417 | |||
1122 | 418 | drizzled::sql::ResultSet result_set(1); | ||
1123 | 419 | |||
1124 | 420 | /* Execute wraps the SQL to run within a transaction */ | ||
1125 | 421 | execute.run(sql, result_set); | ||
1126 | 422 | drizzled::sql::Exception exception= result_set.getException(); | ||
1127 | 423 | |||
1128 | 424 | drizzled::error_t err= exception.getErrorCode(); | ||
1129 | 425 | |||
1130 | 426 | json_out["sqlstate"]= exception.getSQLState(); | ||
1131 | 427 | |||
1132 | 428 | if ((err != drizzled::EE_OK) && (err != drizzled::ER_EMPTY_QUERY)) | ||
1133 | 429 | { | ||
1134 | 430 | json_out["error_type"]="sql error"; | ||
1135 | 431 | json_out["error_message"]= exception.getErrorMessage(); | ||
1136 | 432 | json_out["error_code"]= exception.getErrorCode(); | ||
1137 | 433 | json_out["internal_sql_query"]= sql; | ||
1138 | 434 | json_out["schema"]= "test"; | ||
1139 | 435 | } | ||
1140 | 436 | |||
1141 | 437 | while (result_set.next()) | ||
1142 | 438 | { | ||
1143 | 439 | Json::Value json_row; | ||
1144 | 440 | bool got_error = false; | ||
1145 | 441 | for (size_t x= 0; x < result_set.getMetaData().getColumnCount() && got_error == false; x++) | ||
1146 | 442 | { | ||
1147 | 443 | if (not result_set.isNull(x)) | ||
1148 | 444 | { | ||
1149 | 445 | // The values are now serialized json. We must first | ||
1150 | 446 | // parse them to make them part of this structure, only to immediately | ||
1151 | 447 | // serialize them again in the next step. For large json documents | ||
1152 | 448 | // stored into the blob this must be very, very inefficient. | ||
1153 | 449 | // TODO: Implement a smarter way to push the blob value directly to the client. Probably need to hand code some string appending magic. | ||
1154 | 450 | // TODO: Massimo knows of a library to create JSON in streaming mode. | ||
1155 | 451 | Json::Value json_doc; | ||
1156 | 452 | Json::Reader readrow(json_conf); | ||
1157 | 453 | std::string col_name = result_set.getColumnInfo(x).col_name; | ||
1158 | 454 | bool r = readrow.parse(result_set.getString(x), json_doc); | ||
1159 | 455 | if (r != true) { | ||
1160 | 456 | json_out["error_type"]="json parse error on row value"; | ||
1161 | 457 | json_out["error_internal_sql_column"]=col_name; | ||
1162 | 458 | json_out["error_message"]= reader.getFormatedErrorMessages(); | ||
1163 | 459 | // Just put the string there as it is, better than nothing. | ||
1164 | 460 | json_row[col_name]= result_set.getString(x); | ||
1165 | 461 | got_error=true; | ||
1166 | 462 | break; | ||
1167 | 463 | } | ||
1168 | 464 | else { | ||
1169 | 465 | json_row[col_name]= json_doc; | ||
1170 | 466 | } | ||
1171 | 467 | } | ||
1172 | 468 | } | ||
1173 | 469 | // When done, append this to result set tree | ||
1174 | 470 | json_out["result_set"].append(json_row); | ||
1175 | 471 | } | ||
1176 | 472 | |||
1177 | 473 | json_out["query"]= json_in; | ||
1178 | 474 | } | ||
1179 | 475 | // Return either the results or an error message, in json. | ||
1180 | 476 | Json::StyledWriter writer; | ||
1181 | 477 | std::string output= writer.write(json_out); | ||
1182 | 478 | evbuffer_add(buf, output.c_str(), output.length()); | ||
1183 | 479 | evhttp_send_reply(req, http_response_code, http_response_text, buf); | ||
1184 | 480 | } | ||
1185 | 481 | |||
1186 | 482 | /** | ||
1187 | 483 | * Input json document or update existing one from HTTP POST (and PUT?). | ||
1188 | 484 | * | ||
1189 | 485 | * If json document specifies _id field, then record is updated. If it doesn't | ||
1190 | 486 | * exist, then a new record is created with that _id. | ||
1191 | 487 | * | ||
1192 | 488 | * If _id field is not specified, then a new record is created using | ||
1193 | 489 | * auto_increment value. The _id of the created value is returned in the http | ||
1194 | 490 | * response. | ||
1195 | 491 | * | ||
1196 | 492 | * @todo If there are multiple errors, last one overwrites the previous in json_out. Make them lists. | ||
1197 | 493 | * | ||
1198 | 494 | * @param req should contain a "table" parameter in request uri. "schema" is optional. | ||
1199 | 495 | * @return a json document is returned to client with evhttp_send_reply() | ||
1200 | 496 | */ | ||
1201 | 497 | void process_api02_json_post_req(struct evhttp_request *req, void* ) | ||
1202 | 498 | { | ||
1203 | 499 | bool table_exists = true; | ||
1204 | 500 | Json::Value json_out; | ||
1205 | 501 | |||
1206 | 502 | struct evbuffer *buf = evbuffer_new(); | ||
1207 | 503 | if (buf == NULL) return; | ||
1208 | 504 | |||
1209 | 505 | // Read from http to string "input". | ||
1210 | 506 | std::string input; | ||
1211 | 507 | char buffer[1024]; | ||
1212 | 508 | int l=0; | ||
1213 | 509 | do { | ||
1214 | 510 | l= evbuffer_remove(req->input_buffer, buffer, 1024); | ||
1215 | 511 | input.append(buffer, l); | ||
1216 | 512 | }while(l); | ||
1217 | 513 | |||
1218 | 514 | // Schema and table are given in request uri. | ||
1219 | 515 | // TODO: If we want to be really NoSQL, we will some day allow to use synonyms like "collection" instead of "table". | ||
1220 | 516 | const char *schema; | ||
1221 | 517 | const char *table; | ||
1222 | 518 | const char *id; | ||
1223 | 519 | evhttp_parse_query(evhttp_request_uri(req), req->input_headers); | ||
1224 | 520 | schema = (char *)evhttp_find_header(req->input_headers, "schema"); | ||
1225 | 521 | table = (char *)evhttp_find_header(req->input_headers, "table"); | ||
1226 | 522 | id = (char *)evhttp_find_header(req->input_headers, "_id"); | ||
1227 | 523 | |||
1228 | 524 | // Set test as default schema | ||
1229 | 525 | if ( !strcmp( schema, "") || schema == NULL) | ||
1230 | 526 | { | ||
1231 | 527 | schema = "test"; | ||
1232 | 528 | } | ||
1233 | 529 | |||
1234 | 530 | // Parse "input" into "json_in". | ||
1235 | 531 | Json::Value json_in; | ||
1236 | 532 | Json::Features json_conf; | ||
1237 | 533 | Json::Reader reader(json_conf); | ||
1238 | 534 | bool retval = reader.parse(input, json_in); | ||
1239 | 535 | if (retval != true) { | ||
1240 | 536 | json_out["error_type"]="json error"; | ||
1241 | 537 | json_out["error_message"]= reader.getFormatedErrorMessages(); | ||
1242 | 538 | } | ||
1243 | 539 | else { | ||
1244 | 540 | // It is allowed to specify _id in the uri and leave it out from the json query. | ||
1245 | 541 | // In that case we put the value from uri into json_in here. | ||
1246 | 542 | // If both are specified, the one existing in json_in wins. (This is still valid, no error.) | ||
1247 | 543 | if ( ! json_in["_id"].asBool() ) | ||
1248 | 544 | { | ||
1249 | 545 | if( id ) { | ||
1250 | 546 | json_in["_id"] = (Json::Value::UInt) atol(id); | ||
1251 | 547 | } | ||
1252 | 548 | } | ||
1253 | 549 | |||
1254 | 550 | // For POST method, we check if table exists. | ||
1255 | 551 | // If it doesn't, we automatically CREATE TABLE that matches the structure | ||
1256 | 552 | // in the given json document. (This means, your first JSON document must | ||
1257 | 553 | // contain all top-level keys you'd like to use.) | ||
1258 | 554 | drizzled::Session::shared_ptr _session= drizzled::Session::make_shared(drizzled::plugin::Listen::getNullClient(), | ||
1259 | 555 | drizzled::catalog::local()); | ||
1260 | 556 | drizzled::identifier::user::mptr user_id= identifier::User::make_shared(); | ||
1261 | 557 | user_id->setUser(""); | ||
1262 | 558 | _session->setUser(user_id); | ||
1263 | 559 | drizzled::Execute execute(*(_session.get()), true); | ||
1264 | 560 | |||
1265 | 561 | drizzled::sql::ResultSet result_set(1); | ||
1266 | 562 | std::string sql="select count(*) from information_schema.tables where table_schema = '"; | ||
1267 | 563 | sql.append(schema); | ||
1268 | 564 | sql.append("' AND table_name = '"); | ||
1269 | 565 | sql.append(table); sql.append("';"); | ||
1270 | 566 | /* Execute wraps the SQL to run within a transaction */ | ||
1271 | 567 | execute.run(sql, result_set); | ||
1272 | 568 | |||
1273 | 569 | drizzled::sql::Exception exception= result_set.getException(); | ||
1274 | 570 | |||
1275 | 571 | drizzled::error_t err= exception.getErrorCode(); | ||
1276 | 572 | while(result_set.next()) | ||
1277 | 573 | { | ||
1278 | 574 | if(result_set.getString(0)=="0") | ||
1279 | 575 | { | ||
1280 | 576 | table_exists = false; | ||
1281 | 577 | } | ||
1282 | 578 | } | ||
1283 | 579 | if(table_exists == false) | ||
1284 | 580 | { | ||
1285 | 581 | std::string tmp = "CREATE TABLE "; | ||
1286 | 582 | tmp.append(schema); | ||
1287 | 583 | tmp.append("."); | ||
1288 | 584 | tmp.append(table); | ||
1289 | 585 | tmp.append(" (_id BIGINT PRIMARY KEY auto_increment,"); | ||
1290 | 586 | // Iterate over json_in keys | ||
1291 | 587 | Json::Value::Members createKeys( json_in.getMemberNames() ); | ||
1292 | 588 | for ( Json::Value::Members::iterator it = createKeys.begin(); it != createKeys.end(); ++it ) | ||
1293 | 589 | { | ||
1294 | 590 | const std::string &key = *it; | ||
1295 | 591 | if(key=="_id") { | ||
1296 | 592 | continue; | ||
1297 | 593 | } | ||
1298 | 594 | tmp.append(key); | ||
1299 | 595 | tmp.append(" TEXT"); | ||
1300 | 596 | if( it !=createKeys.end()-1 && key !="_id") | ||
1301 | 597 | { | ||
1302 | 598 | tmp.append(","); | ||
1303 | 599 | } | ||
1304 | 600 | } | ||
1305 | 601 | tmp.append(")"); | ||
1306 | 602 | vector<string> csql; | ||
1307 | 603 | csql.clear(); | ||
1308 | 604 | csql.push_back("COMMIT"); | ||
1309 | 605 | csql.push_back (tmp); | ||
1310 | 606 | sql.clear(); | ||
1311 | 607 | BOOST_FOREACH(string& it, csql) | ||
1312 | 608 | { | ||
1313 | 609 | sql.append(it); | ||
1314 | 610 | sql.append("; "); | ||
1315 | 611 | } | ||
1316 | 612 | drizzled::sql::ResultSet createtable_result_set(1); | ||
1317 | 613 | execute.run(sql, createtable_result_set); | ||
1318 | 614 | |||
1319 | 615 | exception= createtable_result_set.getException(); | ||
1320 | 616 | err= exception.getErrorCode(); | ||
1321 | 617 | } | ||
1322 | 618 | // Now we "parse" the json_in object and build an SQL query | ||
1323 | 619 | sql.clear(); | ||
1324 | 620 | sql.append("REPLACE INTO `"); | ||
1325 | 621 | sql.append(schema); | ||
1326 | 622 | sql.append("`.`"); | ||
1327 | 623 | sql.append(table); | ||
1328 | 624 | sql.append("` SET "); | ||
1329 | 625 | // Iterate over json_in keys | ||
1330 | 626 | Json::Value::Members keys( json_in.getMemberNames() ); | ||
1331 | 627 | for ( Json::Value::Members::iterator it = keys.begin(); it != keys.end(); ++it ) | ||
1332 | 628 | { | ||
1333 | 629 | if ( it != keys.begin() ) | ||
1334 | 630 | { | ||
1335 | 631 | sql.append(", "); | ||
1336 | 632 | } | ||
1337 | 633 | // TODO: Need to do json_in[].type() first and juggle it from there to be safe. See json/value.h | ||
1338 | 634 | const std::string &key = *it; | ||
1339 | 635 | sql.append(key); sql.append("="); | ||
1340 | 636 | Json::StyledWriter writeobject; | ||
1341 | 637 | switch ( json_in[key].type() ) | ||
1342 | 638 | { | ||
1343 | 639 | case Json::nullValue: | ||
1344 | 640 | sql.append("NULL"); | ||
1345 | 641 | break; | ||
1346 | 642 | case Json::intValue: | ||
1347 | 643 | case Json::uintValue: | ||
1348 | 644 | case Json::realValue: | ||
1349 | 645 | case Json::booleanValue: | ||
1350 | 646 | sql.append(json_in[key].asString()); | ||
1351 | 647 | break; | ||
1352 | 648 | case Json::stringValue: | ||
1353 | 649 | sql.append("'\""); | ||
1354 | 650 | // TODO: MUST be sql quoted! | ||
1355 | 651 | sql.append(json_in[key].asString()); | ||
1356 | 652 | sql.append("\"'"); | ||
1357 | 653 | break; | ||
1358 | 654 | case Json::arrayValue: | ||
1359 | 655 | case Json::objectValue: | ||
1360 | 656 | sql.append("'"); | ||
1361 | 657 | sql.append(writeobject.write(json_in[key])); | ||
1362 | 658 | sql.append("'"); | ||
1363 | 659 | break; | ||
1364 | 660 | default: | ||
1365 | 661 | sql.append("'Error in json_server.cc. This should never happen.'"); | ||
1366 | 662 | json_out["error_type"]="json error"; | ||
1367 | 663 | json_out["error_message"]= "json_in object had a value that wasn't of any of the types that we recognize."; | ||
1368 | 664 | break; | ||
1369 | 665 | } | ||
1370 | 666 | sql.append(" "); | ||
1371 | 667 | } | ||
1372 | 668 | sql.append(";"); | ||
1373 | 669 | drizzled::sql::ResultSet replace_result_set(1); | ||
1374 | 670 | |||
1375 | 671 | // Execute wraps the SQL to run within a transaction | ||
1376 | 672 | execute.run(sql, replace_result_set); | ||
1377 | 673 | |||
1378 | 674 | exception= replace_result_set.getException(); | ||
1379 | 675 | |||
1380 | 676 | err= exception.getErrorCode(); | ||
1381 | 677 | |||
1382 | 678 | json_out["sqlstate"]= exception.getSQLState(); | ||
1383 | 679 | |||
1384 | 680 | // TODO: I should be able to return number of rows inserted/updated. | ||
1385 | 681 | // TODO: Return last_insert_id(); | ||
1386 | 682 | if ((err != drizzled::EE_OK) && (err != drizzled::ER_EMPTY_QUERY)) | ||
1387 | 683 | { | ||
1388 | 684 | json_out["error_type"]="sql error"; | ||
1389 | 685 | json_out["error_message"]= exception.getErrorMessage(); | ||
1390 | 686 | json_out["error_code"]= exception.getErrorCode(); | ||
1391 | 687 | json_out["internal_sql_query"]= sql; | ||
1392 | 688 | json_out["schema"]= "test"; | ||
1393 | 689 | } | ||
1394 | 690 | json_out["query"]= json_in; | ||
1395 | 691 | } | ||
1396 | 692 | // Return either the results or an error message, in json. | ||
1397 | 693 | Json::StyledWriter writer; | ||
1398 | 694 | std::string output= writer.write(json_out); | ||
1399 | 695 | evbuffer_add(buf, output.c_str(), output.length()); | ||
1400 | 696 | evhttp_send_reply(req, HTTP_OK, "OK", buf); | ||
1401 | 697 | } | ||
1402 | 698 | |||
1403 | 699 | /* | ||
1404 | 700 | void process_api02_json_put_req(struct evhttp_request *req, void* ) | ||
1405 | 701 | { | ||
1406 | 702 | struct evbuffer *buf = evbuffer_new(); | ||
1407 | 703 | if (buf == NULL) return; | ||
1408 | 704 | evhttp_send_reply(req, HTTP_OK, "OK", buf); | ||
1409 | 705 | } | ||
1410 | 706 | */ | ||
1411 | 707 | |||
1412 | 708 | void process_api02_json_delete_req(struct evhttp_request *req, void* ) | ||
1413 | 709 | { | ||
1414 | 710 | struct evbuffer *buf = evbuffer_new(); | ||
1415 | 711 | if (buf == NULL) return; | ||
1416 | 712 | |||
1417 | 713 | Json::Value json_out; | ||
1418 | 714 | |||
1419 | 715 | std::string input; | ||
1420 | 716 | char buffer[1024]; | ||
1421 | 717 | |||
1422 | 718 | // Schema and table are given in request uri. | ||
1423 | 719 | // TODO: If we want to be really NoSQL, we will some day allow to use synonyms like "collection" instead of "table". | ||
1424 | 720 | // For GET, also the query is in the uri | ||
1425 | 721 | const char *schema; | ||
1426 | 722 | const char *table; | ||
1427 | 723 | const char *query; | ||
1428 | 724 | const char *id; | ||
1429 | 725 | evhttp_parse_query(evhttp_request_uri(req), req->input_headers); | ||
1430 | 726 | schema = (char *)evhttp_find_header(req->input_headers, "schema"); | ||
1431 | 727 | table = (char *)evhttp_find_header(req->input_headers, "table"); | ||
1432 | 728 | query = (char *)evhttp_find_header(req->input_headers, "query"); | ||
1433 | 729 | id = (char *)evhttp_find_header(req->input_headers, "_id"); | ||
1434 | 730 | |||
1435 | 731 | // query can be null if _id was given | ||
1436 | 732 | if ( query == NULL || strcmp(query, "") == 0 ) | ||
1437 | 733 | { | ||
1438 | 734 | // Empty JSON object | ||
1439 | 735 | query = "{}"; | ||
1440 | 736 | } | ||
1441 | 737 | input.append(query, strlen(query)); | ||
1442 | 738 | |||
1443 | 739 | // Set test as default schema | ||
1444 | 740 | if ( !strcmp( schema, "") || schema == NULL) | ||
1445 | 741 | { | ||
1446 | 742 | schema = "test"; | ||
1447 | 743 | } | ||
1448 | 744 | |||
1449 | 745 | // Parse "input" into "json_in". | ||
1450 | 746 | Json::Value json_in; | ||
1451 | 747 | Json::Features json_conf; | ||
1452 | 748 | json_conf.strictMode(); | ||
1453 | 749 | Json::Reader reader(json_conf); | ||
1454 | 750 | bool retval = reader.parse(input, json_in); | ||
1455 | 751 | if (retval != true) { | ||
1456 | 752 | json_out["error_type"]="json error"; | ||
1457 | 753 | json_out["error_message"]= reader.getFormatedErrorMessages(); | ||
1458 | 754 | } | ||
1459 | 755 | else { | ||
1460 | 756 | // It is allowed to specify _id in the uri and leave it out from the json query. | ||
1461 | 757 | // In that case we put the value from uri into json_in here. | ||
1462 | 758 | // If both are specified, the one existing in json_in wins. (This is still valid, no error.) | ||
1463 | 759 | if ( ! json_in["_id"].asBool() ) | ||
1464 | 760 | { | ||
1465 | 761 | if( id ) { | ||
1466 | 762 | json_in["_id"] = (Json::Value::UInt) atol(id); | ||
1467 | 763 | } | ||
1468 | 764 | } | ||
1469 | 765 | // Now we "parse" the json_in object and build an SQL query | ||
1470 | 766 | char sqlformat[1024] = "DELETE FROM `%s`.`%s` WHERE _id=%i;"; | ||
1471 | 767 | sprintf(buffer, sqlformat, schema, table, json_in["_id"].asInt()); | ||
1472 | 768 | std::string sql = ""; | ||
1473 | 769 | sql.append(buffer, strlen(buffer)); | ||
1474 | 770 | |||
1475 | 771 | // We have sql string. Use Execute API to run it and convert results back to JSON. | ||
1476 | 772 | drizzled::Session::shared_ptr _session= drizzled::Session::make_shared(drizzled::plugin::Listen::getNullClient(), | ||
1477 | 773 | drizzled::catalog::local()); | ||
1478 | 774 | drizzled::identifier::user::mptr user_id= identifier::User::make_shared(); | ||
1479 | 775 | user_id->setUser(""); | ||
1480 | 776 | _session->setUser(user_id); | ||
1481 | 777 | |||
1482 | 778 | drizzled::Execute execute(*(_session.get()), true); | ||
1483 | 779 | |||
1484 | 780 | drizzled::sql::ResultSet result_set(1); | ||
1485 | 781 | |||
1486 | 782 | /* Execute wraps the SQL to run within a transaction */ | ||
1487 | 783 | execute.run(sql, result_set); | ||
1488 | 784 | drizzled::sql::Exception exception= result_set.getException(); | ||
1489 | 785 | |||
1490 | 786 | drizzled::error_t err= exception.getErrorCode(); | ||
1491 | 787 | |||
1492 | 788 | json_out["sqlstate"]= exception.getSQLState(); | ||
1493 | 789 | |||
1494 | 790 | if ((err != drizzled::EE_OK) && (err != drizzled::ER_EMPTY_QUERY)) | ||
1495 | 791 | { | ||
1496 | 792 | json_out["error_type"]="sql error"; | ||
1497 | 793 | json_out["error_message"]= exception.getErrorMessage(); | ||
1498 | 794 | json_out["error_code"]= exception.getErrorCode(); | ||
1499 | 795 | json_out["internal_sql_query"]= sql; | ||
1500 | 796 | json_out["schema"]= "test"; | ||
1501 | 797 | } | ||
1502 | 798 | json_out["query"]= json_in; | ||
1503 | 799 | } | ||
1504 | 800 | // Return either the results or an error message, in json. | ||
1505 | 801 | Json::StyledWriter writer; | ||
1506 | 802 | std::string output= writer.write(json_out); | ||
1507 | 803 | evbuffer_add(buf, output.c_str(), output.length()); | ||
1508 | 804 | evhttp_send_reply(req, HTTP_OK, "OK", buf); | ||
1509 | 805 | |||
1510 | 806 | } | ||
1511 | 807 | |||
1512 | 808 | |||
1513 | 230 | static void shutdown_event(int fd, short, void *arg) | 809 | static void shutdown_event(int fd, short, void *arg) |
1514 | 231 | { | 810 | { |
1515 | 232 | struct event_base *base= (struct event_base *)arg; | 811 | struct event_base *base= (struct event_base *)arg; |
1516 | @@ -302,10 +881,25 @@ | |||
1517 | 302 | return false; | 881 | return false; |
1518 | 303 | } | 882 | } |
1519 | 304 | 883 | ||
1520 | 884 | // These URLs are available. Bind worker method to each of them. | ||
1521 | 885 | // Please group by api version. Also unchanged functions must be copied to next version! | ||
1522 | 305 | evhttp_set_cb(httpd, "/", process_root_request, NULL); | 886 | evhttp_set_cb(httpd, "/", process_root_request, NULL); |
1523 | 887 | // API 0.1 | ||
1524 | 306 | evhttp_set_cb(httpd, "/0.1/version", process_api01_version_req, NULL); | 888 | evhttp_set_cb(httpd, "/0.1/version", process_api01_version_req, NULL); |
1525 | 307 | evhttp_set_cb(httpd, "/0.1/sql", process_api01_sql_req, NULL); | 889 | evhttp_set_cb(httpd, "/0.1/sql", process_api01_sql_req, NULL); |
1527 | 308 | evhttp_set_gencb(httpd, process_request, NULL); | 890 | // API 0.2 |
1528 | 891 | evhttp_set_cb(httpd, "/0.2/version", process_api01_version_req, NULL); | ||
1529 | 892 | evhttp_set_cb(httpd, "/0.2/sql", process_api01_sql_req, NULL); | ||
1530 | 893 | evhttp_set_cb(httpd, "/0.2/json", process_api02_json_req, NULL); | ||
1531 | 894 | // API "latest" and also available in top level | ||
1532 | 895 | evhttp_set_cb(httpd, "/latest/version", process_api01_version_req, NULL); | ||
1533 | 896 | evhttp_set_cb(httpd, "/latest/sql", process_api01_sql_req, NULL); | ||
1534 | 897 | evhttp_set_cb(httpd, "/latest/json", process_api02_json_req, NULL); | ||
1535 | 898 | evhttp_set_cb(httpd, "/version", process_api01_version_req, NULL); | ||
1536 | 899 | evhttp_set_cb(httpd, "/sql", process_api01_sql_req, NULL); | ||
1537 | 900 | evhttp_set_cb(httpd, "/json", process_api02_json_req, NULL); | ||
1538 | 901 | // Catch all does nothing and returns generic message. | ||
1539 | 902 | //evhttp_set_gencb(httpd, process_request, NULL); | ||
1540 | 309 | 903 | ||
1541 | 310 | event_set(&wakeup_event, wakeup_fd[0], EV_READ | EV_PERSIST, shutdown_event, base); | 904 | event_set(&wakeup_event, wakeup_fd[0], EV_READ | EV_PERSIST, shutdown_event, base); |
1542 | 311 | event_base_set(base, &wakeup_event); | 905 | event_base_set(base, &wakeup_event); |
1543 | @@ -367,7 +961,7 @@ | |||
1544 | 367 | DRIZZLE_VERSION_ID, | 961 | DRIZZLE_VERSION_ID, |
1545 | 368 | "json_server", | 962 | "json_server", |
1546 | 369 | "0.1", | 963 | "0.1", |
1548 | 370 | "Stewart Smith", | 964 | "Stewart Smith, Henrik Ingo, Mohit Srivastava", |
1549 | 371 | N_("JSON HTTP interface"), | 965 | N_("JSON HTTP interface"), |
1550 | 372 | PLUGIN_LICENSE_GPL, | 966 | PLUGIN_LICENSE_GPL, |
1551 | 373 | drizzle_plugin::json_server::json_server_init, | 967 | drizzle_plugin::json_server::json_server_init, |
1552 | 374 | 968 | ||
1553 | === modified file 'plugin/json_server/plugin.ac' | |||
1554 | --- plugin/json_server/plugin.ac 2011-04-21 01:27:52 +0000 | |||
1555 | +++ plugin/json_server/plugin.ac 2012-05-20 15:31:19 +0000 | |||
1556 | @@ -1,1 +1,9 @@ | |||
1557 | 1 | PANDORA_HAVE_LIBEVENT | 1 | PANDORA_HAVE_LIBEVENT |
1558 | 2 | |||
1559 | 3 | AS_IF([test "x$ac_cv_libevent" = "xno"], | ||
1560 | 4 | AC_MSG_WARN([libevent not found: not building json_server.])) | ||
1561 | 5 | |||
1562 | 6 | PANDORA_LIBEVENT_RECENT | ||
1563 | 7 | |||
1564 | 8 | AS_IF([test "$pandora_cv_libevent_recent" = "no"], | ||
1565 | 9 | AC_MSG_WARN([Your version of libevent is too old. json_server requires v 2.0 or newer: not building json_server.])) | ||
1566 | 2 | 10 | ||
1567 | === modified file 'plugin/json_server/plugin.ini' | |||
1568 | --- plugin/json_server/plugin.ini 2012-01-14 22:02:03 +0000 | |||
1569 | +++ plugin/json_server/plugin.ini 2012-05-20 15:31:19 +0000 | |||
1570 | @@ -17,5 +17,5 @@ | |||
1571 | 17 | json/json_reader.cpp | 17 | json/json_reader.cpp |
1572 | 18 | json/json_value.cpp | 18 | json/json_value.cpp |
1573 | 19 | json/json_writer.cpp | 19 | json/json_writer.cpp |
1575 | 20 | build_conditional="x${ac_cv_libevent}" = "xyes" -a "x${ac_cv_libcurl}" = "xyes" | 20 | build_conditional="x${ac_cv_libevent}" = "xyes" -a "x$pandora_cv_libevent_recent" = "xyes" -a "x${ac_cv_libcurl}" = "xyes" |
1576 | 21 | ldflags=${LTLIBEVENT} | 21 | ldflags=${LTLIBEVENT} |
Started by upstream project "drizzle-build" build number 1994 11.04-slicehost -174.143. 253.46 in workspace /home/jenkins/ workspace/ drizzle- build-ubuntu- debug
Building remotely on ubuntu-
Deleting project workspace... done
Cleaning workspace... workspace/ drizzle- build-ubuntu- debug workspace/ drizzle- build-ubuntu- debug workspace/ drizzle- build-ubuntu- debug returned 0. Command output: "2556 <email address hidden> build-ubuntu- debug] $ /bin/sh -xe /tmp/hudson1749 308941142382677 .sh pandora- plugin write' autoreconf --install --force --verbose -Wall' libtool. m4:6: PANDORA_LIBTOOL is expanded from... canonical. m4:41: PANDORA_ CANONICAL_ TARGET is expanded from... autoconf/ lang.m4: 126: AC_LANG_SAVE is expanded from... m4sugar/ m4sh.m4: 598: AS_IF is expanded from... autoconf/ general. m4:2019: AC_CACHE_VAL is expanded from... autoconf/ general. m4:2040: AC_CACHE_CHECK is expanded from... header_ stdcxx_ 98.m4:21: AC_CXX_ HEADER_ STDCXX_ 98 is expanded from... check_cxx_ standard. m4:6: PANDORA_ CHECK_CXX_ STANDARD is expanded from... autoconf/ c.m4:252: AC_LANG_CPLUSPLUS is expanded from... autoconf/ general. m4:2602: AC_TRY_COMPILE is expanded from... autoconf/ lang.m4: 135: AC_LANG_RESTORE is expanded from... cstdint. m4:12: PANDORA_CXX_CSTDINT is expanded from... cinttypes. m4:12: PANDORA_ CXX_CINTTYPES is expanded from... visibility. m4:23: PANDORA_ CHECK_VISIBILIT Y is expanded from... visibility. m4:71: PANDORA_ ENABLE_ VISIBILITY is expanded from... autocon. ..
$ bzr branch lp:drizzle/build /home/jenkins/
You have not informed bzr of your Launchpad ID, and you must do this to
write to Launchpad or access private data. See "bzr help launchpad-login".
Branched 2556 revision(s).
Getting local revision...
$ bzr revision-info -d /home/jenkins/
info result: bzr revision-info -d /home/jenkins/
" stderr: ""
RevisionState revno:2556 revid:<email address hidden>
[drizzle-
+ ./config/autorun.sh
: running `python config/
: running `/usr/bin/
autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal --force --warnings=all -I m4 --force
autoreconf: configure.ac: tracing
configure.ac:27: warning: The macro `AC_PROG_LIBTOOL' is obsolete.
configure.ac:27: You should run autoupdate.
aclocal.m4:123: AC_PROG_LIBTOOL is expanded from...
m4/pandora_
m4/pandora_
configure.ac:27: the top level
configure.ac:27: warning: The macro `AC_LANG_SAVE' is obsolete.
configure.ac:27: You should run autoupdate.
../../lib/
../../lib/
../../lib/
../../lib/
m4/pandora_
m4/pandora_
configure.ac:27: warning: The macro `AC_LANG_CPLUSPLUS' is obsolete.
configure.ac:27: You should run autoupdate.
../../lib/
configure.ac:27: warning: The macro `AC_TRY_COMPILE' is obsolete.
configure.ac:27: You should run autoupdate.
../../lib/
configure.ac:27: warning: The macro `AC_LANG_RESTORE' is obsolete.
configure.ac:27: You should run autoupdate.
../../lib/
m4/pandora_
m4/pandora_
configure.ac:27: warning: The macro `AC_CHECK_LIBM' is obsolete.
configure.ac:27: You should run autoupdate.
aclocal.m4:3301: AC_CHECK_LIBM is expanded from...
m4/pandora_
m4/pandora_
configure.ac:27: warning: AC_RUN_IFELSE called without default to allow cross compiling
../../lib/