Skip to content

v.db.connect: Add JSON support #6077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion vector/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ build_program_in_subdir(
grass_dbmiclient
grass_dbmidriver
grass_gis
grass_vector)
grass_vector
grass_parson)

build_program_in_subdir(
v.db.select
Expand Down
2 changes: 1 addition & 1 deletion vector/v.db.connect/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ MODULE_TOPDIR = ../..

PGM=v.db.connect

LIBES = $(VECTORLIB) $(DBMILIB) $(GISLIB)
LIBES = $(VECTORLIB) $(DBMILIB) $(GISLIB) $(PARSONLIB)
DEPENDENCIES = $(VECTORDEP) $(DBMIDEP) $(GISDEP)
EXTRA_INC = $(VECT_INC)
EXTRA_CFLAGS = $(VECT_CFLAGS)
Expand Down
227 changes: 195 additions & 32 deletions vector/v.db.connect/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,22 @@
#include <ctype.h>
#include <math.h>
#include <grass/gis.h>
#include <grass/gjson.h>
#include <grass/vector.h>
#include <grass/dbmi.h>
#include <grass/glocale.h>

enum OutputFormat { PLAIN, CSV, JSON };

int main(int argc, char **argv)
{
char *input;
const char *driver_default, *database_default;

struct GModule *module;
struct Option *inopt, *dbdriver, *dbdatabase, *dbtable, *field_opt, *dbkey,
*sep_opt;
struct Flag *print, *columns, *delete, *shell_print;
*sep_opt, *format_opt;
struct Flag *print, *columns, *delete, *csv_print;
dbDriver *driver;
dbString table_name;
dbTable *table;
Expand All @@ -44,6 +47,11 @@ int main(int argc, char **argv)
char *fieldname;
struct Map_info Map;
char *sep;
enum OutputFormat format;
JSON_Value *root_value = NULL, *conn_value = NULL;
JSON_Array *root_array = NULL;
JSON_Object *conn_object = NULL;
int skip_header = 0;

/* set up the options and flags for the command line parser */

Expand Down Expand Up @@ -79,21 +87,31 @@ int main(int argc, char **argv)
field_opt->gisprompt = "new,layer,layer";

sep_opt = G_define_standard_option(G_OPT_F_SEP);
sep_opt->label = _("Field separator for shell script style output");
sep_opt->label = _("Field separator for printing output");
sep_opt->guisection = _("Print");

format_opt = G_define_standard_option(G_OPT_F_FORMAT);
format_opt->options = "plain,csv,json";
format_opt->required = NO;
format_opt->answer = NULL;
format_opt->descriptions = ("plain;Human readable text output;"
"csv;CSV (Comma Separated Values);"
"json;JSON (JavaScript Object Notation);");

print = G_define_flag();
print->key = 'p';
print->description = _("Print all map connection parameters and exit");
print->guisection = _("Print");

shell_print = G_define_flag();
shell_print->key = 'g';
shell_print->label =
_("Print all map connection parameters in shell script style and exit");
shell_print->description =
_("Format: layer[/layer name] table key database driver");
shell_print->guisection = _("Print");
csv_print = G_define_flag();
csv_print->key = 'g';
csv_print->label = _(
"Print all map connection parameters in a legacy format [deprecated]");
csv_print->description = _(
"Order: layer[/layer name] table key database driver"
"This flag is deprecated and will be removed in a future release. Use "
"format=csv instead.");
csv_print->guisection = _("Print");

columns = G_define_flag();
columns->key = 'c';
Expand All @@ -111,6 +129,47 @@ int main(int argc, char **argv)
if (G_parser(argc, argv))
exit(EXIT_FAILURE);

// If no format option is specified, preserve backward compatibility
if (format_opt->answer == NULL || format_opt->answer[0] == '\0') {
if (csv_print->answer || columns->answer) {
format_opt->answer = "csv";
skip_header = 1;
}
else
format_opt->answer = "plain";
}

if (strcmp(format_opt->answer, "json") == 0) {
format = JSON;
root_value = G_json_value_init_array();
if (root_value == NULL) {
G_fatal_error(_("Failed to initialize JSON array. Out of memory?"));
}
root_array = G_json_array(root_value);
}
else if (strcmp(format_opt->answer, "csv") == 0) {
format = CSV;
}
else {
format = PLAIN;
}

if (format != PLAIN && !print->answer && !csv_print->answer &&
!columns->answer) {
G_fatal_error(
_("The -p or -c flag is required when using the format option."));
}

if (csv_print->answer) {
G_verbose_message(
_("Flag 'g' is deprecated and will be removed in a future "
"release. Please use format=csv instead."));
if (format == JSON) {
G_fatal_error(_("The -g flag cannot be used with format=json. "
"Please select only one output format."));
}
}

/* The check must allow '.' in the name (schema.table) */
/*
if (dbtable->answer) {
Expand Down Expand Up @@ -139,11 +198,11 @@ int main(int argc, char **argv)

sep = G_option_to_separator(sep_opt);

if (print->answer && shell_print->answer)
if (print->answer && csv_print->answer)
G_fatal_error(_("Please choose only one print style"));

Vect_set_open_level(1); /* no topology needed */
if (print->answer || shell_print->answer || columns->answer) {
if (print->answer || csv_print->answer || columns->answer) {
if (Vect_open_old2(&Map, inopt->answer, "", field_opt->answer) < 0)
G_fatal_error(_("Unable to open vector map <%s>"), inopt->answer);
}
Expand All @@ -154,7 +213,7 @@ int main(int argc, char **argv)
Vect_hist_command(&Map);
}

if (print->answer || shell_print->answer || columns->answer) {
if (print->answer || csv_print->answer || columns->answer) {
num_dblinks = Vect_get_num_dblinks(&Map);
if (num_dblinks <= 0) {
/* it is ok if a vector map is not connected o an attribute table */
Expand All @@ -164,27 +223,51 @@ int main(int argc, char **argv)
}
else { /* num_dblinks > 0 */

if (print->answer || shell_print->answer) {
if (!(shell_print->answer)) {
if (print->answer || csv_print->answer) {
if (format == PLAIN) {
fprintf(stdout, _("Vector map <%s> is connected by:\n"),
input);
}
if (!skip_header && format == CSV) {
/* CSV Header */
fprintf(stdout, "%s%s%s%s%s%s%s%s%s%s%s\n", "layer", sep,
"layer_name", sep, "table", sep, "key", sep,
"database", sep, "driver");
}
for (i = 0; i < num_dblinks; i++) {
if ((fi = Vect_get_dblink(&Map, i)) == NULL)
G_fatal_error(_("Database connection not defined"));

if (shell_print->answer) {
if (fi->name)
fprintf(stdout, "%d/%s%s%s%s%s%s%s%s%s\n",
fi->number, fi->name, sep, fi->table, sep,
fi->key, sep, fi->database, sep,
fi->driver);
else
fprintf(stdout, "%d%s%s%s%s%s%s%s%s\n", fi->number,
sep, fi->table, sep, fi->key, sep,
fi->database, sep, fi->driver);
}
else {
switch (format) {
case CSV:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not excited about that, but to have the new take on the CSV format consistent with JSON, we should include CSV header and separate layer number from layer name.

To implement this, CSV needs to have two variants, the backwards compatible one which will be triggered by the flags and the new one which will be triggered by the format option. The backwards compatible one would be removed in the future for v9.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same would apply to -c, i.e., header + backwards compatibility variant. (Also implying that v.info -c format=shell is now wrongly labeled as shell, not CSV and needs the same header+compatibility update.) What do you think @NishantBansal2003 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the best I could think of as of now, so let's go this way instead.
let's open an issue for v.info to track this as well? will look into it later on.

if (skip_header) {
/* For Backward compatibility */
if (fi->name)
fprintf(stdout, "%d/%s%s%s%s%s%s%s%s%s\n",
fi->number, fi->name, sep, fi->table,
sep, fi->key, sep, fi->database, sep,
fi->driver);
else
fprintf(stdout, "%d%s%s%s%s%s%s%s%s\n",
fi->number, sep, fi->table, sep,
fi->key, sep, fi->database, sep,
fi->driver);
}
else {
if (fi->name)
fprintf(stdout, "%d%s%s%s%s%s%s%s%s%s%s\n",
fi->number, sep, fi->name, sep,
fi->table, sep, fi->key, sep,
fi->database, sep, fi->driver);
else
fprintf(stdout, "%d%s%s%s%s%s%s%s%s%s%s\n",
fi->number, sep, "", sep, fi->table,
sep, fi->key, sep, fi->database, sep,
fi->driver);
}
break;

case PLAIN:
if (fi->name) {
fprintf(stdout,
_("layer <%d/%s> table <%s> in database "
Expand All @@ -201,6 +284,34 @@ int main(int argc, char **argv)
fi->number, fi->table, fi->database,
fi->driver, fi->key);
}
break;

case JSON:
conn_value = G_json_value_init_object();
if (conn_value == NULL) {
G_fatal_error(_("Failed to initialize JSON object. "
"Out of memory?"));
}
conn_object = G_json_object(conn_value);

G_json_object_set_number(conn_object, "layer",
fi->number);
if (fi->name)
G_json_object_set_string(conn_object, "layer_name",
fi->name);
else
G_json_object_set_null(conn_object, "layer_name");

G_json_object_set_string(conn_object, "table",
fi->table);
G_json_object_set_string(conn_object, "key", fi->key);
G_json_object_set_string(conn_object, "database",
fi->database);
G_json_object_set_string(conn_object, "driver",
fi->driver);

G_json_array_append_value(root_array, conn_value);
break;
}
}
} /* end print */
Expand Down Expand Up @@ -229,18 +340,70 @@ int main(int argc, char **argv)
G_fatal_error(_("Unable to describe table <%s>"),
fi->table);

if (!skip_header && format != JSON) {
/* CSV Header */
fprintf(stdout, "%s|%s\n", "sql_type", "name");
}

ncols = db_get_table_number_of_columns(table);
for (col = 0; col < ncols; col++) {
fprintf(
stdout, "%s|%s\n",
db_sqltype_name(db_get_column_sqltype(
db_get_table_column(table, col))),
db_get_column_name(db_get_table_column(table, col)));
switch (format) {
case PLAIN:
case CSV:
fprintf(stdout, "%s|%s\n",
db_sqltype_name(db_get_column_sqltype(
db_get_table_column(table, col))),
db_get_column_name(
db_get_table_column(table, col)));
break;

case JSON:
conn_value = G_json_value_init_object();
if (conn_value == NULL) {
G_fatal_error(_("Failed to initialize JSON object. "
"Out of memory?"));
}
conn_object = G_json_object(conn_value);

G_json_object_set_string(
conn_object, "name",
db_get_column_name(
db_get_table_column(table, col)));

int sql_type = db_get_column_sqltype(
db_get_table_column(table, col));
G_json_object_set_string(conn_object, "sql_type",
db_sqltype_name(sql_type));

int c_type = db_sqltype_to_Ctype(sql_type);
G_json_object_set_boolean(conn_object, "is_number",
(c_type == DB_C_TYPE_INT ||
c_type == DB_C_TYPE_DOUBLE));

G_json_array_append_value(root_array, conn_value);
break;
}
}

db_close_database(driver);
db_shutdown_driver(driver);
}

if (format == JSON) {
char *json_string =
G_json_serialize_to_string_pretty(root_value);
if (!json_string) {
G_json_value_free(root_value);
G_fatal_error(
_("Failed to serialize JSON to pretty format."));
}

puts(json_string);

G_json_free_serialized_string(json_string);
G_json_value_free(root_value);
}

} /* end else num_dblinks */
} /* end print/columns */
else { /* define new dbln settings or delete */
Expand Down
Loading
Loading