diff --git a/python/grass/script/db.py b/python/grass/script/db.py index 5591b92d4ca..3b6adcb1c3f 100644 --- a/python/grass/script/db.py +++ b/python/grass/script/db.py @@ -19,8 +19,14 @@ .. sectionauthor:: Martin Landa """ +from __future__ import annotations + import os + +from ctypes import byref + from .core import ( + gisenv, run_command, parse_command, read_command, @@ -137,6 +143,15 @@ def db_connection(force=False, env=None): run_command("db.connect", flags="c", env=env) conn = parse_command("db.connect", flags="g", env=env) + if conn and conn.get("driver") == "sqlite": + gis_env = gisenv() + conn["database"] = ( + conn["database"] + .replace("$GISDBASE", gis_env["GISDBASE"]) + .replace("$LOCATION_NAME", gis_env["LOCATION_NAME"]) + .replace("$MAPSET", gis_env["MAPSET"]) + ) + return conn @@ -233,23 +248,145 @@ def db_table_in_vector(table, mapset=".", env=None): return None -def db_begin_transaction(driver): - """Begin transaction. +class DBHandler: + """DB handler - :return: SQL command as string + Allow execute SQL command(s) in transaction mode. + + Public methods: + + ::execute """ - if driver in {"sqlite", "pg"}: - return "BEGIN" - if driver == "mysql": - return "START TRANSACTION" - return "" + def __init__(self, driver_name: str, database: str) -> None: + """Constructor + + :param driver_name: DB driver name + :param database: database name + """ + self._driver_name = driver_name + self._database = database + self._import_c_interface() + + def _import_c_interface(self): + """Import C interface""" + try: + from grass.lib.dbmi import ( + db_begin_transaction, + db_close_database_shutdown_driver, + db_commit_transaction, + db_execute_immediate, + db_free_string, + db_init_string, + db_set_string, + db_start_driver_open_database, + dbString, + DB_OK, + ) + from grass.lib.gis import G_gisinit + from grass.lib.vector import ( + Map_info, + Vect_subst_var, + ) + except (ImportError, OSError, TypeError) as e: + fatal(_("Unable to import C functions: {e}").format(e)) + + class CInterface: + def __init__(self): + self.db_begin_transaction = db_begin_transaction + self.db_execute_immediate = db_execute_immediate + self.db_free_string = db_free_string + self.db_init_string = db_init_string + self.db_close_database_shutdown_driver = ( + db_close_database_shutdown_driver + ) + self.db_commit_transaction = db_commit_transaction + self.db_set_string = db_set_string + self.db_start_driver_open_database = db_start_driver_open_database + self.dbString = dbString + self.DB_OK = DB_OK + self.G_gisinit = G_gisinit + self.Map_info = Map_info + self.Vect_subst_var = Vect_subst_var + + self._c_interface = CInterface() + + def _init_driver(self): + """Init DB driver""" + map = self._c_interface.Map_info() + self._pdriver = self._c_interface.db_start_driver_open_database( + self._driver_name, + self._c_interface.Vect_subst_var(self._database, byref(map)), + ) + if not self._pdriver: + fatal( + _("Unable to open database <{db}> by driver <{driver}>.").format( + db=self._database, driver=self._driver_name + ) + ) -def db_commit_transaction(driver): - """Commit transaction. + def _begin_transaction(self): + """Begin DB transaction.""" + ret = self._c_interface.db_begin_transaction(self._pdriver) + if ret != self._c_interface.DB_OK: + self._shutdown_driver() + fatal( + _( + "Error while starting database <{db}> transaction by" + " driver <{driver}>." + ).format(db=self._database, driver=self._driver_name) + ) - :return: SQL command as string - """ - if driver in {"sqlite", "pg", "mysql"}: - return "COMMIT" - return "" + def _commit_transaction(self): + """Commit DB transaction.""" + ret = self._c_interface.db_commit_transaction(self._pdriver) + if ret != self._c_interface.DB_OK: + self._shutdown_driver() + fatal( + _( + "Error while commit database <{db}> transaction" + " by driver <{driver}>." + ).format(db=self._database, driver=self._driver_name) + ) + + def _execute(self, sql: str | list | tuple) -> None: + """Execute SQL + + :param sql: SQL command string or list of SQLs commands + """ + stmt = self._c_interface.dbString() + self._c_interface.db_init_string(byref(stmt)) + self._c_interface.db_set_string(byref(stmt), sql) + if ( + self._c_interface.db_execute_immediate(self._pdriver, byref(stmt)) + != self._c_interface.DB_OK + ): + self._c_interface.db_free_string(byref(stmt)) + self._shutdown_driver() + fatal(_("Error while executing SQL <{}>.").format(sql)) + self._c_interface.db_free_string(byref(stmt)) + + def _shutdown_driver(self): + """Close DB and shutdown driver""" + self._c_interface.db_close_database_shutdown_driver(self._pdriver) + + def execute(self, sql: str | list | tuple) -> None: + """Execute SQL + + :param sql: SQL command string or list of SQLs statement + """ + self._c_interface.G_gisinit("") + self._init_driver() + + # Begin DB transaction + self._begin_transaction() + # Execute SQL string + if isinstance(sql, (list, tuple)): + for statement in sql: + self._execute(sql=statement) + else: + self._execute(sql) + # Commit DB transaction + self._commit_transaction() + + self._shutdown_driver() diff --git a/python/grass/temporal/core.py b/python/grass/temporal/core.py index 321c73d5408..beadea487c3 100644 --- a/python/grass/temporal/core.py +++ b/python/grass/temporal/core.py @@ -1521,7 +1521,7 @@ def fetchall(self): def execute_transaction(self, statement, mapset=None): """Execute a transactional SQL statement - The BEGIN and END TRANSACTION statements will be added automatically + The BEGIN and COMMIT statements will be added automatically to the sql statement :param statement: The executable SQL statement or SQL script @@ -1532,9 +1532,9 @@ def execute_transaction(self, statement, mapset=None): connected = True sql_script = "" - sql_script += "BEGIN TRANSACTION;\n" + sql_script += "BEGIN;\n" sql_script += statement - sql_script += "END TRANSACTION;" + sql_script += "COMMIT;" try: if self.dbmi.__name__ == "sqlite3": diff --git a/scripts/db.dropcolumn/db.dropcolumn.py b/scripts/db.dropcolumn/db.dropcolumn.py index 24681b18b23..99bb712e883 100755 --- a/scripts/db.dropcolumn/db.dropcolumn.py +++ b/scripts/db.dropcolumn/db.dropcolumn.py @@ -45,8 +45,9 @@ import sys import string -from grass.exceptions import CalledModuleError import grass.script as gs +from grass.exceptions import CalledModuleError +from grass.script.db import DBHandler def main(): @@ -56,6 +57,8 @@ def main(): driver = options["driver"] force = flags["f"] + db_handler = DBHandler(driver_name=driver, database=database) + # check if DB parameters are set, and if not set them. gs.run_command("db.connect", flags="c") @@ -93,6 +96,7 @@ def main(): ) return 0 + sqls = [] if driver == "sqlite": sqlite3_version = gs.read_command( "db.select", @@ -103,9 +107,9 @@ def main(): ).split(".")[0:2] if [int(i) for i in sqlite3_version] >= [int(i) for i in "3.35".split(".")]: - sql = "ALTER TABLE %s DROP COLUMN %s" % (table, column) if column == "cat": - sql = "DROP INDEX %s_%s; %s" % (table, column, sql) + sqls.append(f"DROP INDEX {table}_{column};") + sqls.append(f"ALTER TABLE {table} DROP COLUMN {column};") else: # for older sqlite3 versions, use old way to remove column colnames = [] @@ -119,24 +123,21 @@ def main(): coltypes = ", ".join(coltypes) cmds = [ - "BEGIN TRANSACTION", "CREATE TEMPORARY TABLE ${table}_backup(${coldef})", "INSERT INTO ${table}_backup SELECT ${colnames} FROM ${table}", "DROP TABLE ${table}", "CREATE TABLE ${table}(${coldef})", "INSERT INTO ${table} SELECT ${colnames} FROM ${table}_backup", "DROP TABLE ${table}_backup", - "COMMIT", ] tmpl = string.Template(";\n".join(cmds)) sql = tmpl.substitute(table=table, coldef=coltypes, colnames=colnames) + sqls.extend(sql.split("\n")) else: - sql = "ALTER TABLE %s DROP COLUMN %s" % (table, column) + sqls.append(f"ALTER TABLE {table} DROP COLUMN {column};") try: - gs.write_command( - "db.execute", input="-", database=database, driver=driver, stdin=sql - ) + db_handler.execute(sql=";".join(sqls)) except CalledModuleError: gs.fatal(_("Cannot continue (problem deleting column)")) diff --git a/scripts/v.db.addcolumn/v.db.addcolumn.py b/scripts/v.db.addcolumn/v.db.addcolumn.py index 2d6c88cd2e5..31ad44bc9de 100755 --- a/scripts/v.db.addcolumn/v.db.addcolumn.py +++ b/scripts/v.db.addcolumn/v.db.addcolumn.py @@ -40,32 +40,14 @@ # % key_desc: name type # %end -import atexit -import os -from pathlib import Path import re -from grass.exceptions import CalledModuleError import grass.script as gs - -rm_files = [] - - -def cleanup(): - for file in rm_files: - if os.path.isfile(file): - try: - os.remove(file) - except Exception as e: - gs.warning( - _("Unable to remove file {file}: {message}").format( - file=file, message=e - ) - ) +from grass.exceptions import CalledModuleError +from grass.script.db import DBHandler def main(): - global rm_files map = options["map"] layer = options["layer"] columns = options["columns"] @@ -100,7 +82,9 @@ def main(): driver = f["driver"] column_existing = gs.vector_columns(map, int(layer)).keys() - add_str = "BEGIN TRANSACTION\n" + db_handler = DBHandler(driver_name=driver, database=database) + + sqls = [] pattern = re.compile(r"\s+") for col in columns: if not col: @@ -120,19 +104,11 @@ def main(): ) continue gs.verbose(_("Adding column <{}> to the table").format(col_name)) - add_str += f'ALTER TABLE {table} ADD COLUMN "{col_name}" {col_type};\n' - add_str += "END TRANSACTION" - sql_file = gs.tempfile() - rm_files.append(sql_file) + sqls.append(f'ALTER TABLE {table} ADD COLUMN "{col_name}" {col_type};') + cols_add_str = ",".join([col[0] for col in columns]) - Path(sql_file).write_text(add_str) try: - gs.run_command( - "db.execute", - input=sql_file, - database=database, - driver=driver, - ) + db_handler.execute(sql=sqls) except CalledModuleError: gs.fatal(_("Error adding columns {}").format(cols_add_str)) # write cmd history: @@ -141,5 +117,4 @@ def main(): if __name__ == "__main__": options, flags = gs.parser() - atexit.register(cleanup) main() diff --git a/scripts/v.db.dropcolumn/v.db.dropcolumn.py b/scripts/v.db.dropcolumn/v.db.dropcolumn.py index fd34f36893f..3511b8a883f 100755 --- a/scripts/v.db.dropcolumn/v.db.dropcolumn.py +++ b/scripts/v.db.dropcolumn/v.db.dropcolumn.py @@ -37,9 +37,9 @@ # % required: yes # %end -import string import grass.script as gs from grass.exceptions import CalledModuleError +from grass.script.db import DBHandler def main(): @@ -60,6 +60,8 @@ def main(): database = f["database"] driver = f["driver"] + db_handler = DBHandler(driver_name=driver, database=database) + if not table: gs.fatal( _( @@ -103,28 +105,20 @@ def main(): colnames = ", ".join([f'"{col}"' for col in colnames]) coltypes = ", ".join(coltypes) - cmds = [ - "BEGIN TRANSACTION", - "CREATE TEMPORARY TABLE ${table}_backup (${coldef})", - "INSERT INTO ${table}_backup SELECT ${colnames} FROM ${table}", - "DROP TABLE ${table}", - "CREATE TABLE ${table}(${coldef})", - "INSERT INTO ${table} SELECT ${colnames} FROM ${table}_backup", - "CREATE UNIQUE INDEX ${table}_cat ON ${table} (${keycol} )", - "DROP TABLE ${table}_backup", - "COMMIT", + sql = [ + f"CREATE TEMPORARY TABLE {table}_backup ({coltypes})", + f"INSERT INTO {table}_backup SELECT {colnames} FROM {table}", + f"DROP TABLE {table}", + f"CREATE TABLE {table}({coltypes})", + f"INSERT INTO {table} SELECT {colnames} FROM {table}_backup", + f"CREATE UNIQUE INDEX {table}_cat ON {table} ({keycol} )", + f"DROP TABLE {table}_backup", ] - tmpl = string.Template(";\n".join(cmds)) - sql = tmpl.substitute( - table=table, coldef=coltypes, colnames=colnames, keycol=keycol - ) else: sql = f'ALTER TABLE {table} DROP COLUMN "{column}"' try: - gs.write_command( - "db.execute", input="-", database=database, driver=driver, stdin=sql - ) + db_handler.execute(sql=sql) except CalledModuleError: gs.fatal(_("Deleting column failed")) diff --git a/scripts/v.db.join/v.db.join.py b/scripts/v.db.join/v.db.join.py index 104bee1b92f..2633491c4e5 100755 --- a/scripts/v.db.join/v.db.join.py +++ b/scripts/v.db.join/v.db.join.py @@ -63,31 +63,14 @@ # % description: Columns to exclude from the other table # %end -import atexit import sys -from pathlib import Path - import grass.script as gs from grass.exceptions import CalledModuleError - -rm_files = [] - - -def cleanup(): - for file_path in rm_files: - try: - file_path.unlink(missing_ok=True) - except Exception as e: - gs.warning( - _("Unable to remove file {file}: {message}").format( - file=file_path, message=e - ) - ) +from grass.script.db import DBHandler def main(): - global rm_files # Include mapset into the name, so we avoid multiple messages about # found in more mapsets. The following generates an error message, while the code # above does not. However, the above checks that the map exists, so we don't @@ -113,6 +96,8 @@ def main(): database = f["database"] driver = f["driver"] + db_handler = DBHandler(driver_name=driver, database=database) + if driver == "dbf": gs.fatal(_("JOIN is not supported for tables stored in DBF format")) @@ -218,32 +203,22 @@ def main(): ) ) - update_str = "BEGIN TRANSACTION\n" + sqls = [] for col in cols_to_update: cur_up_str = ( f"UPDATE {maptable} SET {col} = (SELECT {col} FROM " f"{otable} WHERE " - f"{otable}.{ocolumn}={maptable}.{column});\n" + f"{otable}.{ocolumn}={maptable}.{column});" ) - update_str += cur_up_str - update_str += "END TRANSACTION" - gs.debug(update_str, 1) + sqls.append(cur_up_str) + gs.debug("".join(sqls), 1) gs.verbose( _("Updating columns {columns} of vector map {map_name}...").format( columns=", ".join(cols_to_update.keys()), map_name=vector_map ) ) - sql_file = Path(gs.tempfile()) - rm_files.append(sql_file) - sql_file.write_text(update_str, encoding="UTF8") - try: - gs.run_command( - "db.execute", - input=str(sql_file), - database=database, - driver=driver, - ) + db_handler.execute(sql=sqls) except CalledModuleError: gs.fatal(_("Error filling columns {}").format(cols_to_update)) @@ -255,5 +230,4 @@ def main(): if __name__ == "__main__": options, flags = gs.parser() - atexit.register(cleanup) sys.exit(main()) diff --git a/scripts/v.db.univar/tests/conftest.py b/scripts/v.db.univar/tests/conftest.py index 0d121bbb97a..ddafb0592d6 100644 --- a/scripts/v.db.univar/tests/conftest.py +++ b/scripts/v.db.univar/tests/conftest.py @@ -7,15 +7,28 @@ import pytest import grass.script as gs +from grass.script.db import DBHandler def updates_as_transaction(table, cat_column, column, cats, values): - """Create SQL statement for categories and values for a given column""" - sql = ["BEGIN TRANSACTION"] + """Create SQL statement for categories and values for a given column + + :param str table: DB table name + :param str cat_column: DB table cat column name + :param str column: DB table update column name + :param list cats: DB table cat column values + :param str column: DB table update column name + :param list values: DB table update column values + + :return sqls: SQLs + :rtype list + """ + sqls = [] for cat, value in zip(cats, values): - sql.append(f"UPDATE {table} SET {column} = {value} WHERE {cat_column} = {cat};") - sql.append("END TRANSACTION") - return "\n".join(sql) + sqls.append( + f"UPDATE {table} SET {column} = {value} WHERE {cat_column} = {cat};" + ) + return sqls def value_update_by_category(map_name, layer, column_name, cats, values, env): @@ -25,16 +38,15 @@ def value_update_by_category(map_name, layer, column_name, cats, values, env): database = db_info["database"] driver = db_info["driver"] cat_column = "cat" - sql = updates_as_transaction( + sqls = updates_as_transaction( table=table, cat_column=cat_column, column=column_name, cats=cats, values=values, ) - gs.write_command( - "db.execute", input="-", database=database, driver=driver, stdin=sql, env=env - ) + db_handler = DBHandler(driver_name=driver, database=database) + db_handler.execute(sql=sqls) @pytest.fixture(scope="module") diff --git a/scripts/v.dissolve/tests/conftest.py b/scripts/v.dissolve/tests/conftest.py index e5e9a38818d..993b3f09b5b 100644 --- a/scripts/v.dissolve/tests/conftest.py +++ b/scripts/v.dissolve/tests/conftest.py @@ -8,19 +8,37 @@ import grass.script as gs import grass.script.setup as grass_setup +from grass.script.db import DBHandler -def updates_as_transaction(table, cat_column, column, column_quote, cats, values): - """Create SQL statement for categories and values for a given column""" - sql = ["BEGIN TRANSACTION"] +def updates_as_transaction( + table, + cat_column, + column, + column_quote, + cats, + values, +): + """Create SQL statement for categories and values for a given column + + :param str table: DB table name + :param str cat_column: DB table cat column name + :param str column: DB table update column name + :param str column_quote: DB table column quote + :param list cats: DB table cat column values + :param list values: DB table update column values + + :return sqls: SQLs + :rtype list + """ + sqls = [] quote = "'" if column_quote else "" for cat, value in zip(cats, values): - sql.append( + sqls.append( f"UPDATE {table} SET {column} = {quote}{value}{quote} " f"WHERE {cat_column} = {cat};" ) - sql.append("END TRANSACTION") - return "\n".join(sql) + return sqls def value_update_by_category(map_name, layer, column_name, cats, values, env): @@ -32,7 +50,7 @@ def value_update_by_category(map_name, layer, column_name, cats, values, env): cat_column = "cat" column_type = gs.vector_columns(map_name, layer, env=env)[column_name] column_quote = bool(column_type["type"] in {"CHARACTER", "TEXT"}) - sql = updates_as_transaction( + sqls = updates_as_transaction( table=table, cat_column=cat_column, column=column_name, @@ -40,9 +58,8 @@ def value_update_by_category(map_name, layer, column_name, cats, values, env): cats=cats, values=values, ) - gs.write_command( - "db.execute", input="-", database=database, driver=driver, stdin=sql, env=env - ) + db_handler = DBHandler(driver_name=driver, database=database) + db_handler.execute(sql=sqls) @pytest.fixture(scope="module") diff --git a/scripts/v.dissolve/v.dissolve.py b/scripts/v.dissolve/v.dissolve.py index 7336833060a..3145dc7cce0 100755 --- a/scripts/v.dissolve/v.dissolve.py +++ b/scripts/v.dissolve/v.dissolve.py @@ -83,7 +83,6 @@ import grass.script as gs from grass.exceptions import CalledModuleError - # Methods supported by v.db.univar by default. UNIVAR_METHODS = [ "n", @@ -196,22 +195,30 @@ def sql_escape(text): def updates_to_sql(table, updates): - """Create SQL from a list of dicts with column, value, where""" - sql = ["BEGIN TRANSACTION"] + """Create SQL from a list of dicts with column, value, where + + :param str table: DB table name + :param list updates: DB table updates + + :return sqls: SQLs + :rtype list + """ + sqls = [] for update in updates: quote = quote_from_type(update.get("type", None)) value = update["value"] sql_value = f"{quote}{sql_escape(value) if value else 'NULL'}{quote}" - sql.append( + sqls.append( f"UPDATE {table} SET {update['column']} = {sql_value} " f"WHERE {update['where']};" ) - sql.append("END TRANSACTION") - return "\n".join(sql) + return sqls def update_columns(output_name, output_layer, updates, add_columns): """Update attribute values based on a list of updates""" + from grass.script.db import DBHandler + if add_columns: gs.run_command( "v.db.addcolumn", @@ -220,14 +227,11 @@ def update_columns(output_name, output_layer, updates, add_columns): columns=",".join(add_columns), ) db_info = gs.vector_db(output_name)[int(output_layer)] - sql = updates_to_sql(table=db_info["table"], updates=updates) - gs.write_command( - "db.execute", - input="-", - database=db_info["database"], - driver=db_info["driver"], - stdin=sql, - ) + driver = db_info["driver"] + database = db_info["database"] + sqls = updates_to_sql(table=db_info["table"], updates=updates) + db_handler = DBHandler(driver_name=driver, database=database) + db_handler.execute(sql=sqls) def column_value_to_where(column, value, *, quote): @@ -549,7 +553,6 @@ def option_as_list(options, name): def main(): """Run the dissolve operation based on command line parameters""" - options, unused_flags = gs.parser() input_vector = options["input"] output = options["output"] layer = options["layer"] @@ -684,4 +687,5 @@ def main(): if __name__ == "__main__": + options, unused_flags = gs.parser() main() diff --git a/scripts/v.rast.stats/v.rast.stats.py b/scripts/v.rast.stats/v.rast.stats.py index 4096aa49caf..57200f91b6b 100644 --- a/scripts/v.rast.stats/v.rast.stats.py +++ b/scripts/v.rast.stats/v.rast.stats.py @@ -76,9 +76,11 @@ import sys import os import atexit + import grass.script as gs -from grass.script.utils import decode from grass.exceptions import CalledModuleError +from grass.script.db import DBHandler +from grass.script.utils import decode def cleanup(): @@ -95,7 +97,6 @@ def main(): rastertmp = False # setup temporary files tmp = gs.tempfile() - sqltmp = tmp + ".sql" # we need a random name tmpname = gs.basename(tmp) @@ -201,6 +202,8 @@ def main(): # calculate statistics: gs.message(_("Processing input data (%d categories)...") % number) + db_handler = DBHandler(driver_name=fi["driver"], database=fi["database"]) + for i in range(len(rasters)): raster = rasters[i] @@ -208,11 +211,8 @@ def main(): vector, layer, percentile, colprefixes[i], basecols, dbfdriver, flags["c"] ) - # get rid of any earlier attempts - gs.try_remove(sqltmp) - # do the stats - perform_stats( + sqls = perform_stats( raster, percentile, fi, @@ -227,9 +227,7 @@ def main(): gs.message(_("Updating the database ...")) exitcode = 0 try: - gs.run_command( - "db.execute", input=sqltmp, database=fi["database"], driver=fi["driver"] - ) + db_handler.execute(sql=sqls) gs.verbose( _( "Statistics calculated from raster map <{raster}>" @@ -442,48 +440,50 @@ def perform_stats( colnames, extstat, ): - with open(sqltmp, "w") as f: - # do the stats - p = gs.pipe_command( - "r.univar", - flags="t" + extstat, - map=raster, - zones=rastertmp, - percentile=percentile, - sep=";", - ) + sqls = [] + + # do the stats + p = gs.pipe_command( + "r.univar", + flags="t" + extstat, + map=raster, + zones=rastertmp, + percentile=percentile, + sep=";", + ) - first_line = 1 - - f.write("{0}\n".format(gs.db_begin_transaction(fi["driver"]))) - for line in p.stdout: - if first_line: - first_line = 0 - continue - - vars = decode(line).rstrip("\r\n").split(";") - - f.write("UPDATE %s SET" % fi["table"]) - first_var = 1 - for colname in colnames: - variable = colname.replace("%s_" % colprefix, "", 1) - if dbfdriver: - variable = variables_dbf[variable] - i = variables[variable] - value = vars[i] - # convert nan, +nan, -nan, inf, +inf, -inf, Infinity, +Infinity, - # -Infinity to NULL - if value.lower().endswith("nan") or "inf" in value.lower(): - value = "NULL" - if not first_var: - f.write(" , ") - else: - first_var = 0 - f.write(" %s=%s" % (colname, value)) - - f.write(" WHERE %s=%s;\n" % (fi["key"], vars[0])) - f.write("{0}\n".format(gs.db_commit_transaction(fi["driver"]))) - p.wait() + first_line = 1 + + for line in p.stdout: + if first_line: + first_line = 0 + continue + + vars = decode(line).rstrip("\r\n").split(";") + + sql = "" + sql += f"UPDATE {fi['table']} SET" + first_var = 1 + for colname in colnames: + variable = colname.replace("%s_" % colprefix, "", 1) + if dbfdriver: + variable = variables_dbf[variable] + i = variables[variable] + value = vars[i] + # convert nan, +nan, -nan, inf, +inf, -inf, Infinity, +Infinity, + # -Infinity to NULL + if value.lower().endswith("nan") or "inf" in value.lower(): + value = "NULL" + if not first_var: + sql += " , " + else: + first_var = 0 + sql += f" {colname}={value}" + + sql += f" WHERE {fi['key']}={vars[0]};" + sqls.append(sql) + p.wait() + return sqls if __name__ == "__main__":