From 28829d5c6dc6c40a35ed58c8dd6e897276f10ec6 Mon Sep 17 00:00:00 2001 From: Kriti Birda Date: Thu, 11 Jul 2024 00:30:29 +0530 Subject: [PATCH] db.describe: add json support --- db/db.describe/Makefile | 2 +- db/db.describe/db.describe.html | 109 ++++++++++ db/db.describe/local_proto.h | 11 +- db/db.describe/main.c | 87 +++++++- db/db.describe/printtab.c | 157 +++++++++++---- db/db.describe/testsuite/test_dbdescribe.py | 213 ++++++++++++++++++++ 6 files changed, 528 insertions(+), 51 deletions(-) diff --git a/db/db.describe/Makefile b/db/db.describe/Makefile index ce8ce0afd16..ca6caf23313 100644 --- a/db/db.describe/Makefile +++ b/db/db.describe/Makefile @@ -1,7 +1,7 @@ MODULE_TOPDIR = ../.. -LIBES = $(DBMILIB) $(GISLIB) +LIBES = $(DBMILIB) $(GISLIB) $(PARSONLIB) DEPENDENCIES = $(DBMIDEP) $(GISDEP) PGM = db.describe diff --git a/db/db.describe/db.describe.html b/db/db.describe/db.describe.html index 2fe979d74ce..9a363badee5 100644 --- a/db/db.describe/db.describe.html +++ b/db/db.describe/db.describe.html @@ -61,6 +61,115 @@

DBF example

[...] +

JSON Output

+
+db.describe mysoils
+
+ +
+{
+    "table": "mysoils",
+    "description": "",
+    "insert": null,
+    "delete": null,
+    "ncols": 7,
+    "nrows": 1428,
+    "columns": [
+        {
+            "position": 1,
+            "column": "cat",
+            "description": "",
+            "type": "INTEGER",
+            "length": 20,
+            "scale": 0,
+            "precision": 0,
+            "default": null,
+            "nullok": true,
+            "select": null,
+            "update": null
+        },
+        {
+            "position": 2,
+            "column": "OBJECTID",
+            "description": "",
+            "type": "INTEGER",
+            "length": 20,
+            "scale": 0,
+            "precision": 0,
+            "default": null,
+            "nullok": true,
+            "select": null,
+            "update": null
+        },
+        {
+            "position": 3,
+            "column": "AREA",
+            "description": "",
+            "type": "DOUBLE PRECISION",
+            "length": 20,
+            "scale": 0,
+            "precision": 0,
+            "default": null,
+            "nullok": true,
+            "select": null,
+            "update": null
+        },
+        {
+            "position": 4,
+            "column": "PERIMETER",
+            "description": "",
+            "type": "DOUBLE PRECISION",
+            "length": 20,
+            "scale": 0,
+            "precision": 0,
+            "default": null,
+            "nullok": true,
+            "select": null,
+            "update": null
+        },
+        {
+            "position": 5,
+            "column": "GSLNC250_",
+            "description": "",
+            "type": "INTEGER",
+            "length": 20,
+            "scale": 0,
+            "precision": 0,
+            "default": null,
+            "nullok": true,
+            "select": null,
+            "update": null
+        },
+        {
+            "position": 6,
+            "column": "GSLNC250_I",
+            "description": "",
+            "type": "INTEGER",
+            "length": 20,
+            "scale": 0,
+            "precision": 0,
+            "default": null,
+            "nullok": true,
+            "select": null,
+            "update": null
+        },
+        {
+            "position": 7,
+            "column": "GSL_NAME",
+            "description": "",
+            "type": "CHARACTER",
+            "length": 6,
+            "scale": 0,
+            "precision": 0,
+            "default": null,
+            "nullok": true,
+            "select": null,
+            "update": null
+        }
+    ]
+}
+
+

SEE ALSO

diff --git a/db/db.describe/local_proto.h b/db/db.describe/local_proto.h index af669ae46f1..6f7c4b9e682 100644 --- a/db/db.describe/local_proto.h +++ b/db/db.describe/local_proto.h @@ -1,8 +1,13 @@ #ifndef __LOCAL_PROTO_H__ #define __LOCAL_PROTO_H__ -int print_priv(char *, int); -int print_column_definition(dbColumn *); -int print_table_definition(dbDriver *, dbTable *); +#include + +enum OutputFormat { PLAIN, JSON }; + +int print_priv(char *, int, enum OutputFormat, JSON_Object *); +int print_column_definition(dbColumn *, int, enum OutputFormat, JSON_Array *); +int print_table_definition(dbDriver *, dbTable *, enum OutputFormat, + JSON_Object *, JSON_Array *); #endif /* __LOCAL_PROTO_H__ */ diff --git a/db/db.describe/main.c b/db/db.describe/main.c index b2e6398b447..94cab9b0d3b 100644 --- a/db/db.describe/main.c +++ b/db/db.describe/main.c @@ -19,11 +19,13 @@ #include #include #include +#include #include "local_proto.h" struct { char *driver, *database, *table; int printcolnames; + enum OutputFormat format; } parms; /* function prototypes */ @@ -40,7 +42,25 @@ int main(int argc, char **argv) char buf[1024]; dbString stmt; + JSON_Object *root_object, *col_object; + JSON_Value *root_value, *cols_value, *col_value; + JSON_Array *cols_array; + parse_command_line(argc, argv); + + if (parms.format == JSON) { + root_value = json_value_init_object(); + if (root_value == NULL) { + G_fatal_error(_("Failed to initialize JSON array. Out of memory?")); + } + root_object = json_object(root_value); + cols_value = json_value_init_array(); + if (cols_value == NULL) { + G_fatal_error(_("Failed to initialize JSON array. Out of memory?")); + } + cols_array = json_array(cols_value); + } + if (!db_table_exists(parms.driver, parms.database, parms.table)) { G_warning(_("Table <%s> not found in database <%s> using driver <%s>"), parms.table, parms.database, parms.driver); @@ -63,7 +83,8 @@ int main(int argc, char **argv) db_get_string(&table_name)); if (!parms.printcolnames) - print_table_definition(driver, table); + print_table_definition(driver, table, parms.format, root_object, + cols_array); else { ncols = db_get_table_number_of_columns(table); @@ -71,17 +92,57 @@ int main(int argc, char **argv) sprintf(buf, "select * from %s", db_get_table_name(table)); db_set_string(&stmt, buf); nrows = db_get_table_number_of_rows(driver, &stmt); - fprintf(stdout, "ncols: %d\n", ncols); - fprintf(stdout, "nrows: %d\n", nrows); + + switch (parms.format) { + case PLAIN: + fprintf(stdout, "ncols: %d\n", ncols); + fprintf(stdout, "nrows: %d\n", nrows); + break; + case JSON: + json_object_set_number(root_object, "ncols", ncols); + json_object_set_number(root_object, "nrows", nrows); + break; + } + for (col = 0; col < ncols; col++) { column = db_get_table_column(table, col); - fprintf(stdout, "Column %d: %s:%s:%d\n", (col + 1), - db_get_column_name(column), - db_sqltype_name(db_get_column_sqltype(column)), - db_get_column_length(column)); + + switch (parms.format) { + case PLAIN: + fprintf(stdout, "Column %d: %s:%s:%d\n", (col + 1), + db_get_column_name(column), + db_sqltype_name(db_get_column_sqltype(column)), + db_get_column_length(column)); + break; + case JSON: + col_value = json_value_init_object(); + col_object = json_object(col_value); + json_object_set_number(col_object, "position", col + 1); + json_object_set_string(col_object, "name", + db_get_column_name(column)); + json_object_set_string( + col_object, "type", + db_sqltype_name(db_get_column_sqltype(column))); + json_object_set_number(col_object, "length", + db_get_column_length(column)); + json_array_append_value(cols_array, col_value); + break; + } } } + if (parms.format == JSON) { + json_object_set_value(root_object, "columns", cols_value); + char *serialized_string = NULL; + serialized_string = json_serialize_to_string_pretty(root_value); + if (serialized_string == NULL) { + G_fatal_error(_("Failed to initialize pretty JSON string.")); + } + puts(serialized_string); + json_free_serialized_string(serialized_string); + json_value_free(root_value); + } + db_close_database(driver); db_shutdown_driver(driver); @@ -90,7 +151,7 @@ int main(int argc, char **argv) static void parse_command_line(int argc, char **argv) { - struct Option *driver, *database, *table; + struct Option *driver, *database, *table, *format_opt; struct Flag *cols; struct GModule *module; const char *drv, *db; @@ -115,6 +176,9 @@ static void parse_command_line(int argc, char **argv) if ((db = db_get_default_database_name())) database->answer = (char *)db; + format_opt = G_define_standard_option(G_OPT_F_FORMAT); + format_opt->guisection = _("Print"); + /* Set description */ module = G_define_module(); G_add_keyword(_("database")); @@ -128,4 +192,11 @@ static void parse_command_line(int argc, char **argv) parms.database = database->answer; parms.table = table->answer; parms.printcolnames = cols->answer; + + if (strcmp(format_opt->answer, "json") == 0) { + parms.format = JSON; + } + else { + parms.format = PLAIN; + } } diff --git a/db/db.describe/printtab.c b/db/db.describe/printtab.c index 1412f23adcf..1751eb37498 100644 --- a/db/db.describe/printtab.c +++ b/db/db.describe/printtab.c @@ -1,19 +1,31 @@ #include #include #include "local_proto.h" -#include +#include -int print_table_definition(dbDriver *driver, dbTable *table) +int print_table_definition(dbDriver *driver, dbTable *table, + enum OutputFormat format, JSON_Object *root_object, + JSON_Array *cols_array) { int ncols, col, nrows; dbColumn *column; char buf[1024]; dbString stmt; - fprintf(stdout, "table:%s\n", db_get_table_name(table)); - fprintf(stdout, "description:%s\n", db_get_table_description(table)); - print_priv("insert", db_get_table_insert_priv(table)); - print_priv("delete", db_get_table_delete_priv(table)); + switch (format) { + case PLAIN: + fprintf(stdout, "table:%s\n", db_get_table_name(table)); + fprintf(stdout, "description:%s\n", db_get_table_description(table)); + break; + case JSON: + json_object_set_string(root_object, "table", db_get_table_name(table)); + json_object_set_string(root_object, "description", + db_get_table_description(table)); + break; + } + + print_priv("insert", db_get_table_insert_priv(table), format, root_object); + print_priv("delete", db_get_table_delete_priv(table), format, root_object); ncols = db_get_table_number_of_columns(table); @@ -21,58 +33,125 @@ int print_table_definition(dbDriver *driver, dbTable *table) sprintf(buf, "select * from %s", db_get_table_name(table)); db_set_string(&stmt, buf); nrows = db_get_table_number_of_rows(driver, &stmt); - fprintf(stdout, "ncols:%d\n", ncols); - fprintf(stdout, "nrows:%d\n", nrows); + + switch (format) { + case PLAIN: + fprintf(stdout, "ncols:%d\n", ncols); + fprintf(stdout, "nrows:%d\n", nrows); + break; + case JSON: + json_object_set_number(root_object, "ncols", ncols); + json_object_set_number(root_object, "nrows", nrows); + break; + } + for (col = 0; col < ncols; col++) { column = db_get_table_column(table, col); - fprintf(stdout, "\n"); - print_column_definition(column); + print_column_definition(column, col + 1, format, cols_array); } return 0; } -int print_column_definition(dbColumn *column) +int print_column_definition(dbColumn *column, int position, + enum OutputFormat format, JSON_Array *cols_array) { + JSON_Object *col_object; + JSON_Value *col_value; + dbString value_string; - fprintf(stdout, "column:%s\n", db_get_column_name(column)); - fprintf(stdout, "description:%s\n", db_get_column_description(column)); - fprintf(stdout, "type:%s\n", - db_sqltype_name(db_get_column_sqltype(column))); - fprintf(stdout, "len:%d\n", db_get_column_length(column)); - fprintf(stdout, "scale:%d\n", db_get_column_scale(column)); - fprintf(stdout, "precision:%d\n", db_get_column_precision(column)); - fprintf(stdout, "default:"); - if (db_test_column_has_default_value(column)) { - db_init_string(&value_string); - db_convert_column_default_value_to_string(column, &value_string); - fprintf(stdout, "%s", db_get_string(&value_string)); + switch (format) { + case PLAIN: + fprintf(stdout, "\n"); + fprintf(stdout, "column:%s\n", db_get_column_name(column)); + fprintf(stdout, "description:%s\n", db_get_column_description(column)); + fprintf(stdout, "type:%s\n", + db_sqltype_name(db_get_column_sqltype(column))); + fprintf(stdout, "len:%d\n", db_get_column_length(column)); + fprintf(stdout, "scale:%d\n", db_get_column_scale(column)); + fprintf(stdout, "precision:%d\n", db_get_column_precision(column)); + fprintf(stdout, "default:"); + if (db_test_column_has_default_value(column)) { + db_init_string(&value_string); + db_convert_column_default_value_to_string(column, &value_string); + fprintf(stdout, "%s", db_get_string(&value_string)); + } + fprintf(stdout, "\n"); + fprintf(stdout, "nullok:%s\n", + db_test_column_null_allowed(column) ? "yes" : "no"); + break; + case JSON: + col_value = json_value_init_object(); + col_object = json_object(col_value); + json_object_set_number(col_object, "position", position); + json_object_set_string(col_object, "column", + db_get_column_name(column)); + json_object_set_string(col_object, "description", + db_get_column_description(column)); + json_object_set_string(col_object, "type", + db_sqltype_name(db_get_column_sqltype(column))); + json_object_set_number(col_object, "length", + db_get_column_length(column)); + json_object_set_number(col_object, "scale", + db_get_column_scale(column)); + json_object_set_number(col_object, "precision", + db_get_column_precision(column)); + if (db_test_column_has_default_value(column)) { + db_init_string(&value_string); + db_convert_column_default_value_to_string(column, &value_string); + json_object_set_string(col_object, "default", + db_get_string(&value_string)); + } + else { + json_object_set_null(col_object, "default"); + } + json_object_set_boolean(col_object, "nullok", + db_test_column_null_allowed(column)); + break; + } + print_priv("select", db_get_column_select_priv(column), format, col_object); + print_priv("update", db_get_column_update_priv(column), format, col_object); + if (format == JSON) { + json_array_append_value(cols_array, col_value); } - fprintf(stdout, "\n"); - fprintf(stdout, "nullok:%s\n", - db_test_column_null_allowed(column) ? "yes" : "no"); - print_priv("select", db_get_column_select_priv(column)); - print_priv("update", db_get_column_update_priv(column)); return 0; } -int print_priv(char *label, int priv) +int print_priv(char *label, int priv, enum OutputFormat format, + JSON_Object *root_object) { - fprintf(stdout, "%s:", label); - switch (priv) { - case DB_GRANTED: - fprintf(stdout, "yes"); - break; - case DB_NOT_GRANTED: - fprintf(stdout, "no"); + switch (format) { + case PLAIN: + fprintf(stdout, "%s:", label); + switch (priv) { + case DB_GRANTED: + fprintf(stdout, "yes"); + break; + case DB_NOT_GRANTED: + fprintf(stdout, "no"); + break; + default: + fprintf(stdout, "?"); + break; + } + fprintf(stdout, "\n"); break; - default: - fprintf(stdout, "?"); + case JSON: + switch (priv) { + case DB_GRANTED: + json_object_set_boolean(root_object, label, 1); + break; + case DB_NOT_GRANTED: + json_object_set_boolean(root_object, label, 0); + break; + default: + json_object_set_null(root_object, label); + break; + } break; } - fprintf(stdout, "\n"); return 0; } diff --git a/db/db.describe/testsuite/test_dbdescribe.py b/db/db.describe/testsuite/test_dbdescribe.py index 1d6cc82aca5..f48debf0388 100644 --- a/db/db.describe/testsuite/test_dbdescribe.py +++ b/db/db.describe/testsuite/test_dbdescribe.py @@ -5,6 +5,7 @@ @author: lucadelu """ +import json from grass.gunittest.case import TestCase from grass.gunittest.main import test @@ -166,6 +167,202 @@ Column 12: SHAPE_Area:DOUBLE PRECISION:20 """ +output_json = { + "table": "zipcodes", + "description": "", + "insert": None, + "delete": None, + "ncols": 12, + "nrows": 44, + "columns": [ + { + "column": "cat", + "description": "", + "type": "INTEGER", + "length": 20, + "scale": 0, + "position": 1, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "OBJECTID", + "description": "", + "type": "INTEGER", + "length": 20, + "scale": 0, + "position": 2, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "WAKE_ZIPCO", + "description": "", + "type": "DOUBLE PRECISION", + "length": 20, + "scale": 0, + "position": 3, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "PERIMETER", + "description": "", + "type": "DOUBLE PRECISION", + "length": 20, + "scale": 0, + "position": 4, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "ZIPCODE_", + "description": "", + "type": "DOUBLE PRECISION", + "length": 20, + "scale": 0, + "position": 5, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "ZIPCODE_ID", + "description": "", + "type": "DOUBLE PRECISION", + "length": 20, + "scale": 0, + "position": 6, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "ZIPNAME", + "description": "", + "type": "CHARACTER", + "length": 15, + "scale": 0, + "position": 7, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "ZIPNUM", + "description": "", + "type": "DOUBLE PRECISION", + "length": 20, + "scale": 0, + "position": 8, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "ZIPCODE", + "description": "", + "type": "CHARACTER", + "length": 30, + "scale": 0, + "position": 9, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "NAME", + "description": "", + "type": "CHARACTER", + "length": 30, + "scale": 0, + "position": 10, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "SHAPE_Leng", + "description": "", + "type": "DOUBLE PRECISION", + "length": 20, + "scale": 0, + "position": 11, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + { + "column": "SHAPE_Area", + "description": "", + "type": "DOUBLE PRECISION", + "length": 20, + "scale": 0, + "position": 12, + "precision": 0, + "default": None, + "nullok": True, + "select": None, + "update": None, + }, + ], +} + +outcol_json = { + "ncols": 12, + "nrows": 44, + "columns": [ + {"position": 1, "name": "cat", "type": "INTEGER", "length": 20}, + {"position": 2, "name": "OBJECTID", "type": "INTEGER", "length": 20}, + {"position": 3, "name": "WAKE_ZIPCO", "type": "DOUBLE PRECISION", "length": 20}, + {"position": 4, "name": "PERIMETER", "type": "DOUBLE PRECISION", "length": 20}, + {"position": 5, "name": "ZIPCODE_", "type": "DOUBLE PRECISION", "length": 20}, + {"position": 6, "name": "ZIPCODE_ID", "type": "DOUBLE PRECISION", "length": 20}, + {"position": 7, "name": "ZIPNAME", "type": "CHARACTER", "length": 15}, + {"position": 8, "name": "ZIPNUM", "type": "DOUBLE PRECISION", "length": 20}, + {"position": 9, "name": "ZIPCODE", "type": "CHARACTER", "length": 30}, + {"position": 10, "name": "NAME", "type": "CHARACTER", "length": 30}, + { + "position": 11, + "name": "SHAPE_Leng", + "type": "DOUBLE PRECISION", + "length": 20, + }, + { + "position": 12, + "name": "SHAPE_Area", + "type": "DOUBLE PRECISION", + "length": 20, + }, + ], +} + class TestDbCopy(TestCase): invect = "zipcodes" @@ -185,6 +382,22 @@ def test_columns(self): ) self.assertEqual(first=cols, second=outcol) + def test_describe_json(self): + cols = read_command( + "db.describe", table=self.invect, database=self.mapset, format="json" + ) + self.assertEqual(output_json, json.loads(cols)) + + def test_columns_json(self): + cols = read_command( + "db.describe", + table=self.invect, + flags="c", + database=self.mapset, + format="json", + ) + self.assertEqual(outcol_json, json.loads(cols)) + if __name__ == "__main__": test()