1#!/usr/local/bin/python3.8
2############################################################################
3#
4# MODULE:       v.db.reconnect.all
5# AUTHOR(S):    Radim Blazek
6#               Converted to Python by Glynn Clements
7#               Update for GRASS 7 by Markus Metz
8# PURPOSE:      Reconnect all vector maps from the current mapset
9# COPYRIGHT:    (C) 2004, 2012 by the GRASS Development Team
10#
11#               This program is free software under the GNU General
12#               Public License (>=v2). Read the file COPYING that
13#               comes with GRASS for details.
14#
15#############################################################################
16
17#%module
18#% description: Reconnects attribute tables for all vector maps from the current mapset to a new database.
19#% keyword: vector
20#% keyword: attribute table
21#% keyword: database
22#%end
23#%flag
24#% key: c
25#% description: Copy attribute tables to the target database if not exist
26#%end
27#%flag
28#% key: d
29#% description: Delete attribute tables from the source database
30#%end
31#%option G_OPT_DB_DATABASE
32#% key: old_database
33#% description: Name of source database
34#%end
35#%option G_OPT_DB_SCHEMA
36#% key: old_schema
37#% label: Name of source database schema
38#%end
39#%option
40#% key: new_driver
41#% description: Name for target driver
42#%end
43#%option G_OPT_DB_DATABASE
44#% key: new_database
45#% description: Name for target database
46#%end
47#%option G_OPT_DB_SCHEMA
48#% key: new_schema
49#% label: Name for target database schema
50#%end
51
52import sys
53import os
54import string
55
56import grass.script as gscript
57from grass.exceptions import CalledModuleError
58
59# substitute variables (gisdbase, location_name, mapset)
60
61
62def substitute_db(database):
63    gisenv = gscript.gisenv()
64    tmpl = string.Template(database)
65
66    return tmpl.substitute(GISDBASE=gisenv['GISDBASE'],
67                           LOCATION_NAME=gisenv['LOCATION_NAME'],
68                           MAPSET=gisenv['MAPSET'])
69
70# create database if doesn't exist
71
72
73def create_db(driver, database):
74    subst_database = substitute_db(database)
75    if driver == 'dbf':
76        path = subst_database
77        # check if destination directory exists
78        if not os.path.isdir(path):
79            # create dbf database
80            os.makedirs(path)
81            return True
82        return False
83
84    if driver == 'sqlite':
85        path = os.path.dirname(subst_database)
86        # check if destination directory exists
87        if not os.path.isdir(path):
88            os.makedirs(path)
89
90    if subst_database in gscript.read_command('db.databases', quiet=True,
91                                              driver=driver).splitlines():
92        return False
93
94    gscript.info(_("Target database doesn't exist, "
95                   "creating a new database using <%s> driver...") % driver)
96    try:
97        gscript.run_command('db.createdb', driver=driver,
98                            database=subst_database)
99    except CalledModuleError:
100        gscript.fatal(_("Unable to create database <%s> by driver <%s>") %
101                      (subst_database, driver))
102
103    return False
104
105# copy tables if required (-c)
106
107
108def copy_tab(from_driver, from_database, from_table,
109             to_driver, to_database, to_table):
110    if to_table in gscript.read_command('db.tables', quiet=True,
111                                        driver=to_driver,
112                                        database=to_database,
113                                        stderr=nuldev).splitlines():
114        return False
115
116    gscript.info("Copying table <%s> to target database..." % to_table)
117    try:
118        gscript.run_command('db.copy', from_driver=from_driver,
119                            from_database=from_database,
120                            from_table=from_table, to_driver=to_driver,
121                            to_database=to_database,
122                            to_table=to_table)
123    except CalledModuleError:
124        gscript.fatal(_("Unable to copy table <%s>") % from_table)
125
126    return True
127
128# drop tables if required (-d)
129
130
131def drop_tab(vector, layer, table, driver, database):
132    # disconnect
133    try:
134        gscript.run_command('v.db.connect', flags='d', quiet=True, map=vector,
135                            layer=layer, table=table)
136    except CalledModuleError:
137        gscript.warning(_("Unable to disconnect table <%s> from vector <%s>") %
138                        (table, vector))
139    # drop table
140    try:
141        gscript.run_command('db.droptable', quiet=True, flags='f',
142                            driver=driver, database=database,
143                            table=table)
144    except CalledModuleError:
145        gscript.fatal(_("Unable to drop table <%s>") % table)
146
147# create index on key column
148
149
150def create_index(driver, database, table, index_name, key):
151    if driver == 'dbf':
152        return False
153
154    gscript.info(_("Creating index <%s>...") % index_name)
155    try:
156        gscript.run_command('db.execute', quiet=True, driver=driver,
157                            database=database,
158                            sql="create unique index %s on %s(%s)" %
159                                (index_name, table, key))
160    except CalledModuleError:
161        gscript.warning(_("Unable to create index <%s>") % index_name)
162
163
164def main():
165    # old connection
166    old_database = options['old_database']
167    old_schema = options['old_schema']
168    # new connection
169    default_connection = gscript.db_connection()
170    if options['new_driver']:
171        new_driver = options['new_driver']
172    else:
173        new_driver = default_connection['driver']
174    if options['new_database']:
175        new_database = options['new_database']
176    else:
177        new_database = default_connection['database']
178    if options['new_schema']:
179        new_schema = options['new_schema']
180    else:
181        new_schema = default_connection['schema']
182
183    if old_database == '':
184        old_database = None
185    old_database_subst = None
186    if old_database is not None:
187        old_database_subst = substitute_db(old_database)
188
189    new_database_subst = substitute_db(new_database)
190
191    if old_database_subst == new_database_subst and old_schema == new_schema:
192        gscript.fatal(_("Old and new database connection is identical. "
193                        "Nothing to do."))
194
195    mapset = gscript.gisenv()['MAPSET']
196
197    vectors = gscript.list_grouped('vect')[mapset]
198    num_vectors = len(vectors)
199
200    if flags['c']:
201        # create new database if not existing
202        create_db(new_driver, new_database)
203
204    i = 0
205    for vect in vectors:
206        vect = "%s@%s" % (vect, mapset)
207        i += 1
208        gscript.message(_("%s\nReconnecting vector map <%s> "
209                          "(%d of %d)...\n%s") %
210                        ('-' * 80, vect, i, num_vectors, '-' * 80))
211        for f in gscript.vector_db(vect, stderr=nuldev).values():
212            layer = f['layer']
213            schema_table = f['table']
214            key = f['key']
215            database = f['database']
216            driver = f['driver']
217
218            # split schema.table
219            if '.' in schema_table:
220                schema, table = schema_table.split('.', 1)
221            else:
222                schema = ''
223                table = schema_table
224
225            if new_schema:
226                new_schema_table = "%s.%s" % (new_schema, table)
227            else:
228                new_schema_table = table
229
230            gscript.debug("DATABASE = '%s' SCHEMA = '%s' TABLE = '%s' ->\n"
231                          "      NEW_DATABASE = '%s' NEW_SCHEMA_TABLE = '%s'" %
232                          (old_database, schema, table, new_database,
233                           new_schema_table))
234
235            do_reconnect = True
236            if old_database_subst is not None:
237                if database != old_database_subst:
238                    do_reconnect = False
239            if database == new_database_subst:
240                do_reconnect = False
241            if schema != old_schema:
242                do_reconnect = False
243
244            if do_reconnect == True:
245                gscript.verbose(_("Reconnecting layer %d...") % layer)
246
247                if flags['c']:
248                    # check if table exists in new database
249                    copy_tab(driver, database, schema_table,
250                             new_driver, new_database, new_schema_table)
251
252                # drop original table if required
253                if flags['d']:
254                    drop_tab(
255                        vect,
256                        layer,
257                        schema_table,
258                        driver,
259                        substitute_db(database))
260
261                # reconnect tables (don't use substituted new_database)
262                # NOTE: v.db.connect creates an index on the key column
263                try:
264                    gscript.run_command('v.db.connect', flags='o', quiet=True,
265                                        map=vect, layer=layer,
266                                        driver=new_driver,
267                                        database=new_database,
268                                        table=new_schema_table, key=key)
269                except CalledModuleError:
270                    gscript.warning(_("Unable to connect table <%s> to vector "
271                                      "<%s> on layer <%s>") %
272                                    (table, vect, str(layer)))
273
274            else:
275                if database != new_database_subst:
276                    gscript.warning(_("Layer <%d> will not be reconnected "
277                                      "because database or schema do not "
278                                      "match.") % layer)
279    return 0
280
281if __name__ == "__main__":
282    options, flags = gscript.parser()
283    nuldev = open(os.devnull, 'w')
284    sys.exit(main())
285