[couchdb-glib/wip/query-response] introduce couchdb_session_execute_query



commit 1ae46a976b25081ae4d638f79f9ac499e7eb1a5b
Author: Krzysztof Klimonda <kklimonda syntaxhighlighted com>
Date:   Wed Jan 19 13:31:09 2011 +0100

    introduce couchdb_session_execute_query

 couchdb-glib/couchdb-session.c |  287 ++++++++++++++++++++++++++++++----------
 couchdb-glib/couchdb-session.h |   10 ++-
 2 files changed, 223 insertions(+), 74 deletions(-)
---
diff --git a/couchdb-glib/couchdb-session.c b/couchdb-glib/couchdb-session.c
index c995751..f0706f8 100644
--- a/couchdb-glib/couchdb-session.c
+++ b/couchdb-glib/couchdb-session.c
@@ -70,6 +70,14 @@ static void debug_message (const gchar *log_domain,
 			   const gchar *message, gpointer user_data);
 #endif
 
+static void add_oauth_signature (CouchdbSession *session,
+				 SoupMessage *http_message,
+				 const char *method, const char *url);
+
+static JsonNode *parse_json_response (CouchdbSession * session,
+					SoupMessage * http_message,
+					GError ** error);
+
 static gboolean _session_authenticate(SoupSession *session,
 				      SoupMessage *msg,
 				      SoupAuth *auth,
@@ -197,6 +205,12 @@ couchdb_session_init (CouchdbSession *session)
 #endif
 }
 
+GQuark
+couchdb_session_error_quark ()
+{
+	return g_quark_from_static_string ("couchdb-session-error-quark");
+}
+
 /**
  * couchdb_session_new:
  * @uri: URI of the CouchDB instance to connect to
@@ -232,6 +246,187 @@ couchdb_session_get_uri (CouchdbSession *session)
 }
 
 /**
+ * couchdb_session_execute_query
+ * @self: A #CouchdbSession object
+ * @query: #CouchdbQuery object describing the query
+ * @error: Placeholder for error information
+ *
+ * This method executes the given query on the database and returns
+ * a #CouchdbResponse object which keeps the response returned by CouchDB.
+ *
+ * Return value: (transfer full): A #CouchdbResponse object with the response
+ * from the server, or %NULL if there was an error, in which case @error
+ * will be set approprately.
+ */
+CouchdbResponse *
+couchdb_session_execute_query (CouchdbSession *self, CouchdbQuery *query,
+			       GError **error)
+{
+	const gchar *path, *query_string, *method, *content_type, *etag;
+	gchar *uri, *full_uri, *body, *encoded_dbname;
+	JsonNode *node;
+	JsonObject *object;
+	CouchdbResponse *response;
+	SoupMessage *message;
+	guint status;
+	gsize content_length;
+
+	g_return_val_if_fail (COUCHDB_IS_SESSION (self), NULL);
+	g_return_val_if_fail (COUCHDB_IS_QUERY (query), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	node = NULL;
+	object = NULL;
+	body = NULL;
+	response = NULL;
+
+	path = couchdb_query_get_path (query);
+
+	/* to prevent creating uri with two slashes check if first character
+	   of path is slash and, if it is, skip it. */
+	uri = g_strconcat (self->priv->uri, "/",
+			   path ? (*path == '/' ? path+1 : path) : "", NULL);
+
+	query_string = couchdb_query_get_query_options_string (query);
+	if (query_string != NULL) {
+		full_uri = g_strdup_printf ("%s?%s", uri, query_string);
+
+		g_free (uri);
+
+		uri = full_uri;
+	}
+
+	method = couchdb_query_get_method (query);
+
+	object = couchdb_query_get_json_object (query);
+	if (object != NULL) {
+		JsonGenerator *generator;
+		JsonNode *node;
+
+		node = json_node_new (JSON_NODE_OBJECT);
+		json_node_set_object (node, object);
+
+		generator = json_generator_new ();
+		json_generator_set_root (generator, node);
+		body = json_generator_to_data (generator, NULL);
+
+		g_object_unref (generator);
+		json_node_free (node);
+	}
+
+	message = soup_message_new (method, uri);
+	if (body != NULL) {
+		soup_message_set_request (message, "application/json",
+					  SOUP_MEMORY_COPY, body, strlen (body));
+	}
+
+	if (couchdb_session_is_authentication_enabled (self)) {
+		CouchdbCredentialsType cred_type;
+		CouchdbCredentials *credentials;
+
+		credentials = self->priv->credentials;
+		cred_type = couchdb_credentials_get_auth_type (credentials);
+
+		switch (cred_type) {
+		case COUCHDB_CREDENTIALS_TYPE_OAUTH:
+			add_oauth_signature (self, message, method, uri);
+			break;
+		default:
+			g_warning ("Unknown credentials object, not authenticating");
+			break;
+		}
+	}
+
+#ifdef DEBUG_MESSAGES
+	g_debug ("Sending %s to %s with headers: ", method, uri);
+	soup_message_headers_foreach (message->request_headers,
+				      (SoupMessageHeadersForeachFunc)
+				      debug_print_headers, NULL);
+#endif
+
+	status = soup_session_send_message (self->priv->http_session, message);
+	g_debug ("status is %u", status);
+
+	if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
+		const char *tmp;
+		GError *inner_error;
+		SoupMessageHeaders *headers;
+
+		inner_error = NULL;
+
+		/* node can be NULL (when there was no response) so we
+		   have to pass new GError location and then deal with it */
+		node = parse_json_response (self, message, &inner_error);
+		if (inner_error != NULL) {
+			g_propagate_error (error, inner_error);
+			goto cleanup;
+		}
+
+		/* I'm going to burn in hell for it.. or person responsible
+		   for returning dynamic results from database based on
+		   query. It would be much simpler if we have used JsonBuilder
+		   and JsonReader in our API.
+		   In case of returned result being an array enclose it in
+		   JsonObject with one member called "array" */
+		if (node && JSON_NODE_TYPE (node) == JSON_NODE_ARRAY) {
+			JsonArray *array;
+
+			object = json_object_new ();
+			array = json_node_dup_array (node);
+			json_object_set_array_member (object, "array", array);
+		} else if (node) {
+			object = json_node_dup_object (node);
+		} else {
+			object = NULL;
+		}
+
+		headers = message->response_headers;
+
+		etag = soup_message_headers_get_one (headers, "Etag");
+		content_type =
+			soup_message_headers_get_one (headers, "Content-Type");
+
+		tmp = soup_message_headers_get_one (headers, "Content-Length");
+		if (tmp) {
+			content_length = g_ascii_strtoll (tmp, NULL, 10);
+		}
+
+		response = g_object_new (COUCHDB_TYPE_RESPONSE,
+					 "response", object,
+					 "etag", etag,
+					 "status-code", status,
+					 "content-type", content_type,
+					 "content-length", content_length, NULL);
+	} else {
+		uint error_enum;
+		const char *message;
+		switch (status) {
+		case 409:
+			error_enum = COUCHDB_SESSION_ERROR_CONFLICT;
+			message = "Document Conflict";
+			break;
+		default:
+			error_enum = COUCHDB_SESSION_ERROR_UNKNOWN;
+			message = "Unknown Error";
+			break;
+		}
+		g_set_error_literal (error, COUCHDB_SESSION_ERROR,
+				     error_enum, message);
+	}
+
+cleanup:
+	g_free (uri);
+	g_free (body);
+	if (node)
+		json_node_free (node);
+	if (object)
+		json_object_unref (object);
+	g_object_unref (message);
+
+	return response;
+}
+
+/**
  * couchdb_session_list_databases:
  * @session: A #CouchdbSession object
  * @error: Placeholder for error information
@@ -655,14 +850,22 @@ add_oauth_signature (CouchdbSession *session, SoupMessage *http_message, const c
 #endif /* HAVE_OAUTH */
 }
 
-static gboolean
-parse_json_response (CouchdbSession *session, JsonParser *json_parser, SoupMessage *http_message, GError **error)
+static JsonNode *
+parse_json_response (CouchdbSession * session, SoupMessage * http_message,
+		     GError ** error)
 {
 	SoupBuffer *buffer;
         GString *str = NULL;
         goffset offset = 0;
         gboolean success = TRUE;
-	
+	JsonParser *parser;
+	JsonNode *node;
+
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	node = NULL;
+	parser = json_parser_new ();
+
 	while ((buffer = soup_message_body_get_chunk (http_message->response_body, offset))) {
 		if (!str)
                         str = g_string_new ("");
@@ -676,87 +879,25 @@ parse_json_response (CouchdbSession *session, JsonParser *json_parser, SoupMessa
 #ifdef DEBUG_MESSAGES
 		g_debug ("Response body: %s", str->str);
 #endif
-		if (!json_parser_load_from_data (json_parser,
+		if (!json_parser_load_from_data (parser,
 						 (const gchar *) str->str,
 						 str->len,
 						 error)) {
-			g_object_unref (G_OBJECT (json_parser));
-			g_set_error (error, COUCHDB_ERROR, -1, "Invalid JSON response");
+			g_set_error (error,
+				     COUCHDB_SESSION_ERROR,
+				     COUCHDB_SESSION_ERROR_INVALID_JSON,
+				     "Invalid JSON response");
 			success = FALSE;
 		}
 
 		g_string_free (str, TRUE);
-	}
 
-	return success;
-}
-
-/**
- * couchdb_session_send_message:
- * @session: A #CouchdbSession object
- * @method: HTTP method to use
- * @url: URL to send the message to
- * @body: Body of the HTTP request
- * @output: Placeholder for output information
- * @error: Placeholder for error information
- *
- * This function is used to communicate with CouchDB over HTTP, and should not be used
- * by applications unless they really have a need (like missing API in couchdb-glib which
- * the application needs).
- *
- * Return value: TRUE if successful, FALSE otherwise.
- */
-gboolean
-couchdb_session_send_message (CouchdbSession *session, const char *method, const char *url, const char *body, JsonParser *output, GError **error)
-{
-	SoupMessage *http_message;
-	guint status;
-	GError **real_error;
-	
-	g_return_val_if_fail (COUCHDB_IS_SESSION (session), FALSE);
-	g_return_val_if_fail (method != NULL, FALSE);
-
-	if (error != NULL)
-		real_error = error;
-	else
-		real_error = NULL;
-
-	http_message = soup_message_new (method, url);
-	if (body != NULL) {
-		soup_message_set_request (http_message, "application/json", SOUP_MEMORY_COPY,
-					  body, strlen (body));
+		node = json_node_copy (json_parser_get_root (parser));
 	}
 
-	if (couchdb_session_is_authentication_enabled (session)) {
-		switch (couchdb_credentials_get_auth_type (session->priv->credentials)) {
-		case COUCHDB_CREDENTIALS_TYPE_OAUTH:
-			add_oauth_signature (session, http_message, method, url);
-			break;
-		default:
-			g_warning ("Got unknown credentials object, not authenticating message");
-		}
-	}
-
-#ifdef DEBUG_MESSAGES
-	g_debug ("Sending %s to %s... with headers: ", method, url);
-	soup_message_headers_foreach (http_message->request_headers,
-				      (SoupMessageHeadersForeachFunc) debug_print_headers,
-				      NULL);
-#endif
-	status = soup_session_send_message (session->priv->http_session, http_message);
-	if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
-		if (output != NULL)
-		       	parse_json_response (session, output, http_message, real_error);
-		g_object_unref (G_OBJECT (http_message));
-
-	       	return TRUE;
-	} else {
-		if (error != NULL)
-			g_set_error (error, COUCHDB_ERROR, status, "%s", http_message->reason_phrase);
-		g_object_unref (G_OBJECT (http_message));
+	g_object_unref (G_OBJECT (parser));
 
-		return FALSE;
-	}
+	return node;
 }
 
 #ifdef DEBUG_MESSAGES
diff --git a/couchdb-glib/couchdb-session.h b/couchdb-glib/couchdb-session.h
index e65c852..116b901 100644
--- a/couchdb-glib/couchdb-session.h
+++ b/couchdb-glib/couchdb-session.h
@@ -26,9 +26,11 @@
 
 #include <glib.h>
 #include <glib-object.h>
+#include <json-glib/json-glib.h>
 #include "couchdb-types.h"
 #include "couchdb-credentials.h"
-#include "couchdb-database-info.h"
+#include "couchdb-response.h"
+#include "couchdb-query.h"
 
 G_BEGIN_DECLS
 
@@ -39,6 +41,8 @@ G_BEGIN_DECLS
 #define COUCHDB_IS_SESSION_CLASS(klass)     (G_TYPE_CHECK_CLASS_TYPE ((klass), COUCHDB_TYPE_SESSION))
 #define COUCHDB_SESSION_GET_CLASS(obj)      (G_TYPE_INSTANCE_GET_CLASS ((obj), COUCHDB_TYPE_SESSION, CouchdbSessionClass))
 
+#define COUCHDB_SESSION_ERROR (couchdb_session_error_quark ())
+
 typedef struct _CouchdbSessionPrivate CouchdbSessionPrivate;
 typedef struct _CouchdbDatabase CouchdbDatabase;
 
@@ -67,6 +71,10 @@ GSList              *couchdb_session_list_databases (CouchdbSession *session, GE
 void                 couchdb_session_free_database_list (GSList *dblist);
 
 CouchdbDatabaseInfo *couchdb_session_get_database_info (CouchdbSession *session, const char *dbname, GError **error);
+CouchdbResponse *couchdb_session_execute_query (CouchdbSession * self,
+						CouchdbQuery * query,
+						GError ** error);
+
 
 CouchdbDatabase     *couchdb_session_get_database (CouchdbSession *session, const char *dbname, GError **error);
 gboolean             couchdb_session_create_database (CouchdbSession *session, const char *dbname, GError **error);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]