gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: removing old amount logic from l


From: gnunet
Subject: [taler-exchange] branch master updated: removing old amount logic from libtalerpq
Date: Sun, 30 Jul 2023 12:24:08 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new 35011bf2 removing old amount logic from libtalerpq
35011bf2 is described below

commit 35011bf29b4576b8c63b71538557da1fd3ae657a
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Jul 30 12:24:04 2023 +0200

    removing old amount logic from libtalerpq
---
 src/include/taler_pq_lib.h |  48 ---------
 src/pq/pq_query_helper.c   | 149 +++-------------------------
 src/pq/pq_result_helper.c  | 240 ---------------------------------------------
 src/pq/test_pq.c           | 108 +++++++-------------
 4 files changed, 47 insertions(+), 498 deletions(-)

diff --git a/src/include/taler_pq_lib.h b/src/include/taler_pq_lib.h
index 4b2a1f5d..07e00ca3 100644
--- a/src/include/taler_pq_lib.h
+++ b/src/include/taler_pq_lib.h
@@ -30,28 +30,6 @@
 #include <gnunet/gnunet_pq_lib.h>
 #include "taler_util.h"
 
-/**
- * Generate query parameter for a currency, consisting of the three
- * components "value", "fraction" and "currency" in this order. The
- * types must be a 64-bit integer, 32-bit integer and a
- * #TALER_CURRENCY_LEN-sized BLOB/VARCHAR respectively.
- *
- * @param x pointer to the query parameter to pass
- */
-struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_amount_nbo (const struct TALER_AmountNBO *x);
-
-
-/**
- * Generate query parameter for an amount, consisting of the two
- * components "value" and "fraction" in this order. The
- * types must be a 64-bit integer and a 32-bit integer
- * respectively. The currency is dropped.
- *
- * @param x pointer to the query parameter to pass
- */
-struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_amount (const struct TALER_Amount *x);
 
 /**
  * Generate query parameter (as record tuple) for an amount, consisting
@@ -181,32 +159,6 @@ TALER_PQ_query_param_array_amount (
   const struct TALER_Amount *amounts,
   struct GNUNET_PQ_Context *db);
 
-/**
- * Currency amount expected.
- *
- * @param name name of the field in the table
- * @param currency currency to use for @a amount
- * @param[out] amount where to store the result
- * @return array entry for the result specification to use
- */
-struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_amount_nbo (const char *name,
-                                 const char *currency,
-                                 struct TALER_AmountNBO *amount);
-
-
-/**
- * Currency amount expected.
- *
- * @param name name of the field in the table
- * @param currency currency to use for @a amount
- * @param[out] amount where to store the result
- * @return array entry for the result specification to use
- */
-struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_amount (const char *name,
-                             const char *currency,
-                             struct TALER_Amount *amount);
 
 /**
  * Currency amount expected, from a record-field of (DB) taler_amount type
diff --git a/src/pq/pq_query_helper.c b/src/pq/pq_query_helper.c
index 3b33e336..a216e647 100644
--- a/src/pq/pq_query_helper.c
+++ b/src/pq/pq_query_helper.c
@@ -28,128 +28,6 @@
 #include "pq_common.h"
 
 
-/**
- * Function called to convert input argument into SQL parameters.
- *
- * @param cls closure
- * @param data pointer to input argument, here a `struct TALER_AmountNBO`
- * @param data_len number of bytes in @a data (if applicable)
- * @param[out] param_values SQL data to set
- * @param[out] param_lengths SQL length data to set
- * @param[out] param_formats SQL format data to set
- * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
- * @param[out] scratch buffer for dynamic allocations (to be done via 
GNUNET_malloc()
- * @param scratch_length number of entries left in @a scratch
- * @return -1 on error, number of offsets used in @a scratch otherwise
- */
-static int
-qconv_amount_nbo (void *cls,
-                  const void *data,
-                  size_t data_len,
-                  void *param_values[],
-                  int param_lengths[],
-                  int param_formats[],
-                  unsigned int param_length,
-                  void *scratch[],
-                  unsigned int scratch_length)
-{
-  const struct TALER_AmountNBO *amount = data;
-  unsigned int off = 0;
-
-  (void) cls;
-  (void) scratch;
-  (void) scratch_length;
-  GNUNET_assert (sizeof (struct TALER_AmountNBO) == data_len);
-  GNUNET_assert (2 == param_length);
-  param_values[off] = (void *) &amount->value;
-  param_lengths[off] = sizeof (amount->value);
-  param_formats[off] = 1;
-  off++;
-  param_values[off] = (void *) &amount->fraction;
-  param_lengths[off] = sizeof (amount->fraction);
-  param_formats[off] = 1;
-  return 0;
-}
-
-
-struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_amount_nbo (const struct TALER_AmountNBO *x)
-{
-  struct GNUNET_PQ_QueryParam res = {
-    .conv = &qconv_amount_nbo,
-    .data = x,
-    .size = sizeof (*x),
-    .num_params = 2
-  };
-
-  return res;
-}
-
-
-/**
- * Function called to convert input argument into SQL parameters.
- *
- * @param cls closure
- * @param data pointer to input argument, here a `struct TALER_Amount`
- * @param data_len number of bytes in @a data (if applicable)
- * @param[out] param_values SQL data to set
- * @param[out] param_lengths SQL length data to set
- * @param[out] param_formats SQL format data to set
- * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
- * @param[out] scratch buffer for dynamic allocations (to be done via 
GNUNET_malloc()
- * @param scratch_length number of entries left in @a scratch
- * @return -1 on error, number of offsets used in @a scratch otherwise
- */
-static int
-qconv_amount (void *cls,
-              const void *data,
-              size_t data_len,
-              void *param_values[],
-              int param_lengths[],
-              int param_formats[],
-              unsigned int param_length,
-              void *scratch[],
-              unsigned int scratch_length)
-{
-  const struct TALER_Amount *amount_hbo = data;
-  struct TALER_AmountNBO *amount;
-
-  (void) cls;
-  (void) scratch;
-  (void) scratch_length;
-  GNUNET_assert (2 == param_length);
-  GNUNET_assert (sizeof (struct TALER_AmountNBO) == data_len);
-  amount = GNUNET_new (struct TALER_AmountNBO);
-  scratch[0] = amount;
-  TALER_amount_hton (amount,
-                     amount_hbo);
-  qconv_amount_nbo (cls,
-                    amount,
-                    sizeof (struct TALER_AmountNBO),
-                    param_values,
-                    param_lengths,
-                    param_formats,
-                    param_length,
-                    &scratch[1],
-                    scratch_length - 1);
-  return 1;
-}
-
-
-struct GNUNET_PQ_QueryParam
-TALER_PQ_query_param_amount (const struct TALER_Amount *x)
-{
-  struct GNUNET_PQ_QueryParam res = {
-    .conv = &qconv_amount,
-    .data = x,
-    .size = sizeof (*x),
-    .num_params = 2
-  };
-
-  return res;
-}
-
-
 /**
  * Function called to convert input amount into SQL parameter as tuple.
  *
@@ -185,33 +63,34 @@ qconv_amount_tuple (void *cls,
   GNUNET_assert (1 <= scratch_length);
   GNUNET_assert (sizeof (struct TALER_Amount) == data_len);
   GNUNET_static_assert (sizeof(uint32_t) == sizeof(Oid));
-
   {
     char *out;
-    Oid oid_v, oid_f;
+    Oid oid_v;
+    Oid oid_f;
+
     GNUNET_assert (GNUNET_OK ==
-                   GNUNET_PQ_get_oid_by_name (db, "int8", &oid_v));
+                   GNUNET_PQ_get_oid_by_name (db,
+                                              "int8",
+                                              &oid_v));
     GNUNET_assert (GNUNET_OK ==
-                   GNUNET_PQ_get_oid_by_name (db, "int4", &oid_f));
+                   GNUNET_PQ_get_oid_by_name (db,
+                                              "int4",
+                                              &oid_f));
 
     {
-      struct TALER_PQ_Amount_P d = MAKE_TALER_PQ_AMOUNT_P (db,
-                                                           amount,
-                                                           oid_v,
-                                                           oid_f);
-
+      struct TALER_PQ_Amount_P d
+        = MAKE_TALER_PQ_AMOUNT_P (db,
+                                  amount,
+                                  oid_v,
+                                  oid_f);
       sz = sizeof(uint32_t); /* number of elements in tuple */
       sz += sizeof(d);
-
       out = GNUNET_malloc (sz);
       scratch[0] = out;
-
       *(uint32_t *) out = htonl (2);
       out += sizeof(uint32_t);
-
       *(struct TALER_PQ_Amount_P*) out = d;
     }
-
   }
 
   param_values[0] = scratch[0];
diff --git a/src/pq/pq_result_helper.c b/src/pq/pq_result_helper.c
index ed8100a1..5384bf96 100644
--- a/src/pq/pq_result_helper.c
+++ b/src/pq/pq_result_helper.c
@@ -24,246 +24,6 @@
 #include "taler_pq_lib.h"
 
 
-/**
- * Extract a currency amount from a query result according to the
- * given specification.
- *
- * @param result the result to extract the amount from
- * @param row which row of the result to extract the amount from (needed as 
results can have multiple rows)
- * @param currency currency to use for @a r_amount_nbo
- * @param val_name name of the column with the amount's "value", must include 
the substring "_val".
- * @param frac_name name of the column with the amount's "fractional" value, 
must include the substring "_frac".
- * @param[out] r_amount_nbo where to store the amount, in network byte order
- * @return
- *   #GNUNET_YES if all results could be extracted
- *   #GNUNET_NO if at least one result was NULL
- *   #GNUNET_SYSERR if a result was invalid (non-existing field)
- */
-static enum GNUNET_GenericReturnValue
-extract_amount_nbo_helper (PGresult *result,
-                           int row,
-                           const char *currency,
-                           const char *val_name,
-                           const char *frac_name,
-                           struct TALER_AmountNBO *r_amount_nbo)
-{
-  int val_num;
-  int frac_num;
-  int len;
-
-  /* These checks are simply to check that clients obey by our naming
-     conventions, and not for any functional reason */
-  GNUNET_assert (NULL !=
-                 strstr (val_name,
-                         "_val"));
-  GNUNET_assert (NULL !=
-                 strstr (frac_name,
-                         "_frac"));
-  /* Set return value to invalid in case we don't finish */
-  memset (r_amount_nbo,
-          0,
-          sizeof (struct TALER_AmountNBO));
-  val_num = PQfnumber (result,
-                       val_name);
-  frac_num = PQfnumber (result,
-                        frac_name);
-  if (val_num < 0)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Field `%s' does not exist in result\n",
-                val_name);
-    return GNUNET_SYSERR;
-  }
-  if (frac_num < 0)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Field `%s' does not exist in result\n",
-                frac_name);
-    return GNUNET_SYSERR;
-  }
-  if ( (PQgetisnull (result,
-                     row,
-                     val_num)) ||
-       (PQgetisnull (result,
-                     row,
-                     frac_num)) )
-  {
-    return GNUNET_NO;
-  }
-  /* Note that Postgres stores value in NBO internally,
-     so no conversion needed in this case */
-  r_amount_nbo->value = *(uint64_t *) PQgetvalue (result,
-                                                  row,
-                                                  val_num);
-  r_amount_nbo->fraction = *(uint32_t *) PQgetvalue (result,
-                                                     row,
-                                                     frac_num);
-  if (GNUNET_ntohll (r_amount_nbo->value) >= TALER_AMOUNT_MAX_VALUE)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Field `%s' exceeds legal range\n",
-                val_name);
-    return GNUNET_SYSERR;
-  }
-  if (ntohl (r_amount_nbo->fraction) >= TALER_AMOUNT_FRAC_BASE)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Field `%s' exceeds legal range\n",
-                frac_name);
-    return GNUNET_SYSERR;
-  }
-  len = GNUNET_MIN (TALER_CURRENCY_LEN - 1,
-                    strlen (currency));
-  GNUNET_memcpy (r_amount_nbo->currency,
-                 currency,
-                 len);
-  return GNUNET_OK;
-}
-
-
-/**
- * Extract data from a Postgres database @a result at row @a row.
- *
- * @param cls closure, a `const char *` giving the currency
- * @param result where to extract data from
- * @param row row to extract data from
- * @param fname name (or prefix) of the fields to extract from
- * @param[in,out] dst_size where to store size of result, may be NULL
- * @param[out] dst where to store the result
- * @return
- *   #GNUNET_YES if all results could be extracted
- *   #GNUNET_NO if at least one result was NULL
- *   #GNUNET_SYSERR if a result was invalid (non-existing field)
- */
-static enum GNUNET_GenericReturnValue
-extract_amount_nbo (void *cls,
-                    PGresult *result,
-                    int row,
-                    const char *fname,
-                    size_t *dst_size,
-                    void *dst)
-{
-  const char *currency = cls;
-  char *val_name;
-  char *frac_name;
-  enum GNUNET_GenericReturnValue ret;
-
-  if (sizeof (struct TALER_AmountNBO) != *dst_size)
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_asprintf (&val_name,
-                   "%s_val",
-                   fname);
-  GNUNET_asprintf (&frac_name,
-                   "%s_frac",
-                   fname);
-  ret = extract_amount_nbo_helper (result,
-                                   row,
-                                   currency,
-                                   val_name,
-                                   frac_name,
-                                   dst);
-  GNUNET_free (val_name);
-  GNUNET_free (frac_name);
-  return ret;
-}
-
-
-struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_amount_nbo (const char *name,
-                                 const char *currency,
-                                 struct TALER_AmountNBO *amount)
-{
-  struct GNUNET_PQ_ResultSpec res = {
-    .conv = &extract_amount_nbo,
-    .cls = (void *) currency,
-    .dst = (void *) amount,
-    .dst_size = sizeof (*amount),
-    .fname = name
-  };
-
-  return res;
-}
-
-
-/**
- * Extract data from a Postgres database @a result at row @a row.
- *
- * @param cls closure, a `const char *` giving the currency
- * @param result where to extract data from
- * @param row row to extract data from
- * @param fname name (or prefix) of the fields to extract from
- * @param[in,out] dst_size where to store size of result, may be NULL
- * @param[out] dst where to store the result
- * @return
- *   #GNUNET_YES if all results could be extracted
- *   #GNUNET_NO if at least one result was NULL
- *   #GNUNET_SYSERR if a result was invalid (non-existing field)
- */
-static enum GNUNET_GenericReturnValue
-extract_amount (void *cls,
-                PGresult *result,
-                int row,
-                const char *fname,
-                size_t *dst_size,
-                void *dst)
-{
-  const char *currency = cls;
-  struct TALER_Amount *r_amount = dst;
-  char *val_name;
-  char *frac_name;
-  struct TALER_AmountNBO amount_nbo;
-  enum GNUNET_GenericReturnValue ret;
-
-  if (sizeof (struct TALER_AmountNBO) != *dst_size)
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_asprintf (&val_name,
-                   "%s_val",
-                   fname);
-  GNUNET_asprintf (&frac_name,
-                   "%s_frac",
-                   fname);
-  ret = extract_amount_nbo_helper (result,
-                                   row,
-                                   currency,
-                                   val_name,
-                                   frac_name,
-                                   &amount_nbo);
-  if (GNUNET_OK == ret)
-    TALER_amount_ntoh (r_amount,
-                       &amount_nbo);
-  else
-    memset (r_amount,
-            0,
-            sizeof (struct TALER_Amount));
-  GNUNET_free (val_name);
-  GNUNET_free (frac_name);
-  return ret;
-}
-
-
-struct GNUNET_PQ_ResultSpec
-TALER_PQ_result_spec_amount (const char *name,
-                             const char *currency,
-                             struct TALER_Amount *amount)
-{
-  struct GNUNET_PQ_ResultSpec res = {
-    .conv = &extract_amount,
-    .cls = (void *) currency,
-    .dst = (void *) amount,
-    .dst_size = sizeof (*amount),
-    .fname = name
-  };
-
-  return res;
-}
-
-
 /**
  * Extract an amount from a tuple from a Postgres database @a result at row @a 
row.
  *
diff --git a/src/pq/test_pq.c b/src/pq/test_pq.c
index 218d9ad2..71a2d5a0 100644
--- a/src/pq/test_pq.c
+++ b/src/pq/test_pq.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2015, 2016 Taler Systems SA
+  (C) 2015, 2016, 2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -29,28 +29,20 @@
  * @param db database handle to initialize
  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
  */
-static int
+static enum GNUNET_GenericReturnValue
 postgres_prepare (struct GNUNET_PQ_Context *db)
 {
   struct GNUNET_PQ_PreparedStatement ps[] = {
     GNUNET_PQ_make_prepare ("test_insert",
                             "INSERT INTO test_pq ("
                             " tamount"
-                            ",hamount_val"
-                            ",hamount_frac"
-                            ",namount_val"
-                            ",namount_frac"
                             ",json"
                             ",aamount"
                             ") VALUES "
-                            "($1, $2, $3, $4, $5, $6, $7);"),
+                            "($1, $2, $3);"),
     GNUNET_PQ_make_prepare ("test_select",
                             "SELECT"
                             " tamount"
-                            ",hamount_val"
-                            ",hamount_frac"
-                            ",namount_val"
-                            ",namount_frac"
                             ",json"
                             ",aamount"
                             " FROM test_pq;"),
@@ -71,17 +63,8 @@ static int
 run_queries (struct GNUNET_PQ_Context *conn)
 {
   struct TALER_Amount tamount;
-  struct TALER_Amount hamount;
-  struct TALER_Amount hamount2;
-  struct TALER_AmountNBO namount;
-  struct TALER_AmountNBO namount2;
   struct TALER_Amount aamount[3];
-  struct TALER_Amount *pamount;
-  size_t npamount;
-  PGresult *result;
-  int ret;
   json_t *json;
-  json_t *json2;
 
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount ("EUR:5.3",
@@ -95,28 +78,23 @@ run_queries (struct GNUNET_PQ_Context *conn)
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount ("EUR:7.7",
                                          &tamount));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount ("EUR:5.5",
-                                         &hamount));
-  TALER_amount_hton (&namount,
-                     &hamount);
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount ("EUR:4.4",
-                                         &hamount));
   json = json_object ();
-  json_object_set_new (json, "foo", json_integer (42));
   GNUNET_assert (NULL != json);
+  GNUNET_assert (0 ==
+                 json_object_set_new (json,
+                                      "foo",
+                                      json_integer (42)));
   {
     struct GNUNET_PQ_QueryParam params_insert[] = {
-      TALER_PQ_query_param_amount_tuple (conn, &tamount),
-      TALER_PQ_query_param_amount (&hamount),
-      TALER_PQ_query_param_amount_nbo (&namount),
+      TALER_PQ_query_param_amount_tuple (conn,
+                                         &tamount),
       TALER_PQ_query_param_json (json),
       TALER_PQ_query_param_array_amount (3,
                                          aamount,
                                          conn),
       GNUNET_PQ_query_param_end
     };
+    PGresult *result;
 
     result = GNUNET_PQ_exec_prepared (conn,
                                       "test_insert",
@@ -130,30 +108,22 @@ run_queries (struct GNUNET_PQ_Context *conn)
       return 1;
     }
     PQclear (result);
+    json_decref (json);
   }
   {
+    struct TALER_Amount tamount2;
+    struct TALER_Amount *pamount;
+    size_t npamount;
+    json_t *json2;
     struct GNUNET_PQ_QueryParam params_select[] = {
       GNUNET_PQ_query_param_end
     };
-
-    result = GNUNET_PQ_exec_prepared (conn,
-                                      "test_select",
-                                      params_select);
-    if (1 !=
-        PQntuples (result))
-    {
-      GNUNET_break (0);
-      PQclear (result);
-      return 1;
-    }
-  }
-
-  {
     struct GNUNET_PQ_ResultSpec results_select[] = {
-      TALER_PQ_result_spec_amount ("hamount", "EUR", &hamount2),
-      TALER_PQ_result_spec_amount_nbo ("namount", "EUR", &namount2),
-      TALER_PQ_result_spec_json ("json", &json2),
-      TALER_PQ_result_spec_amount_tuple ("tamount", "EUR", &tamount),
+      TALER_PQ_result_spec_amount_tuple ("tamount",
+                                         "EUR",
+                                         &tamount2),
+      TALER_PQ_result_spec_json ("json",
+                                 &json2),
       TALER_PQ_result_spec_array_amount (conn,
                                          "aamount",
                                          "EUR",
@@ -162,23 +132,21 @@ run_queries (struct GNUNET_PQ_Context *conn)
       GNUNET_PQ_result_spec_end
     };
 
-    ret = GNUNET_PQ_extract_result (result,
-                                    results_select,
-                                    0);
-    GNUNET_break (0 ==
-                  TALER_amount_cmp (&hamount,
-                                    &hamount2));
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_string_to_amount ("EUR:5.5",
-                                           &hamount));
-    TALER_amount_ntoh (&hamount2,
-                       &namount2);
+    if (1 !=
+        GNUNET_PQ_eval_prepared_singleton_select (conn,
+                                                  "test_select",
+                                                  params_select,
+                                                  results_select))
+    {
+      GNUNET_break (0);
+      return 1;
+    }
     GNUNET_break (0 ==
-                  TALER_amount_cmp (&hamount,
-                                    &hamount2));
+                  TALER_amount_cmp (&tamount,
+                                    &tamount2));
     GNUNET_break (42 ==
-                  json_integer_value (json_object_get (json2, "foo")));
-
+                  json_integer_value (json_object_get (json2,
+                                                       "foo")));
     GNUNET_break (3 == npamount);
     for (size_t i = 0; i < 3; i++)
     {
@@ -186,14 +154,8 @@ run_queries (struct GNUNET_PQ_Context *conn)
                     TALER_amount_cmp (&aamount[i],
                                       &pamount[i]));
     }
-
     GNUNET_PQ_cleanup_result (results_select);
-    PQclear (result);
   }
-  json_decref (json);
-  if (GNUNET_OK != ret)
-    return 1;
-
   return 0;
 }
 
@@ -213,10 +175,6 @@ main (int argc,
                             "$$;"),
     GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_pq ("
                             " tamount taler_amount NOT NULL"
-                            ",hamount_val INT8 NOT NULL"
-                            ",hamount_frac INT4 NOT NULL"
-                            ",namount_val INT8 NOT NULL"
-                            ",namount_frac INT4 NOT NULL"
                             ",json VARCHAR NOT NULL"
                             ",aamount taler_amount[]"
                             ")"),

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]