64
64
# %end
65
65
66
66
import atexit
67
- import os
68
67
import sys
69
- import grass .script as grass
68
+
69
+ from pathlib import Path
70
+
71
+ import grass .script as gs
70
72
from grass .exceptions import CalledModuleError
71
73
72
74
rm_files = []
73
75
74
76
75
77
def cleanup ():
76
- for file in rm_files :
77
- if os .path .isfile (file ):
78
- try :
79
- os .remove (file )
80
- except Exception as e :
81
- grass .warning (
82
- _ ("Unable to remove file {file}: {message}" ).format (
83
- file = file , message = e
84
- )
78
+ for file_path in rm_files :
79
+ try :
80
+ file_path .unlink (missing_ok = True )
81
+ except Exception as e :
82
+ gs .warning (
83
+ _ ("Unable to remove file {file}: {message}" ).format (
84
+ file = file_path , message = e
85
85
)
86
+ )
86
87
87
88
88
89
def main ():
89
90
global rm_files
90
- map = options ["map" ]
91
+ # Include mapset into the name, so we avoid multiple messages about
92
+ # found in more mapsets. The following generates an error message, while the code
93
+ # above does not. However, the above checks that the map exists, so we don't
94
+ # check it here.
95
+ vector_map = gs .find_file (options ["map" ], element = "vector" )["fullname" ]
91
96
layer = options ["layer" ]
92
97
column = options ["column" ]
93
98
otable = options ["other_table" ]
94
99
ocolumn = options ["other_column" ]
100
+ scolumns = None
95
101
if options ["subset_columns" ]:
96
102
scolumns = options ["subset_columns" ].split ("," )
97
- else :
98
- scolumns = None
103
+ ecolumns = None
99
104
if options ["exclude_columns" ]:
100
105
ecolumns = options ["exclude_columns" ].split ("," )
101
- else :
102
- ecolumns = None
103
106
104
107
try :
105
- f = grass .vector_layer_db (map , layer )
108
+ f = gs .vector_layer_db (vector_map , layer )
106
109
except CalledModuleError :
107
110
sys .exit (1 )
108
111
109
- # Include mapset into the name, so we avoid multiple messages about
110
- # found in more mapsets. The following generates an error message, while the code
111
- # above does not. However, the above checks that the map exists, so we don't
112
- # check it here.
113
- map = grass .find_file (map , element = "vector" )["fullname" ]
114
-
115
112
maptable = f ["table" ]
116
113
database = f ["database" ]
117
114
driver = f ["driver" ]
118
115
119
116
if driver == "dbf" :
120
- grass .fatal (_ ("JOIN is not supported for tables stored in DBF format" ))
117
+ gs .fatal (_ ("JOIN is not supported for tables stored in DBF format" ))
121
118
122
119
if not maptable :
123
- grass .fatal (
120
+ gs .fatal (
124
121
_ ("There is no table connected to this map. Unable to join any column." )
125
122
)
126
123
124
+ all_cols_tt = gs .vector_columns (vector_map , int (layer )).keys ()
125
+ # This is used for testing presence (and potential name conflict) with
126
+ # the newly added columns, but the test needs to case-insensitive since it
127
+ # is SQL, so we lowercase the names here and in the test
128
+ # An alternative is quoting identifiers (as in e.g. #3634)
129
+ all_cols_tt = [name .lower () for name in all_cols_tt ]
130
+
127
131
# check if column is in map table
128
- if column not in grass . vector_columns ( map , layer ) :
129
- grass .fatal (
132
+ if column . lower () not in all_cols_tt :
133
+ gs .fatal (
130
134
_ ("Column <{column}> not found in table <{table}>" ).format (
131
135
column = column , table = maptable
132
136
)
133
137
)
134
138
135
139
# describe other table
136
- all_cols_ot = grass .db_describe (otable , driver = driver , database = database )["cols" ]
140
+ all_cols_ot = {
141
+ col_desc [0 ].lower (): col_desc [1 :]
142
+ for col_desc in gs .db_describe (otable , driver = driver , database = database )["cols" ]
143
+ }
137
144
138
145
# check if ocolumn is on other table
139
- if ocolumn not in [ ocol [ 0 ] for ocol in all_cols_ot ] :
140
- grass .fatal (
146
+ if ocolumn . lower () not in all_cols_ot :
147
+ gs .fatal (
141
148
_ ("Column <{column}> not found in table <{table}>" ).format (
142
149
column = ocolumn , table = otable
143
150
)
@@ -146,106 +153,107 @@ def main():
146
153
# determine columns subset from other table
147
154
if not scolumns :
148
155
# select all columns from other table
149
- cols_to_add = all_cols_ot
156
+ cols_to_update = all_cols_ot
150
157
else :
151
- cols_to_add = []
158
+ cols_to_update = {}
152
159
# check if scolumns exists in the other table
153
160
for scol in scolumns :
154
- found = False
155
- for col_ot in all_cols_ot :
156
- if scol == col_ot [0 ]:
157
- found = True
158
- cols_to_add .append (col_ot )
159
- break
160
- if not found :
161
- grass .warning (
161
+ if scol not in all_cols_ot :
162
+ gs .warning (
162
163
_ ("Column <{column}> not found in table <{table}>" ).format (
163
164
column = scol , table = otable
164
165
)
165
166
)
167
+ else :
168
+ cols_to_update [scol ] = all_cols_ot [scol ]
169
+
170
+ # skip the vector column which is used for join
171
+ if column in cols_to_update :
172
+ cols_to_update .pop (column )
166
173
167
174
# exclude columns from other table
168
175
if ecolumns :
169
- cols_to_add = list (filter (lambda col : col [0 ] not in ecolumns , cols_to_add ))
170
-
171
- all_cols_tt = grass .vector_columns (map , int (layer )).keys ()
172
- # This is used for testing presence (and potential name conflict) with
173
- # the newly added columns, but the test needs to case-insensitive since it
174
- # is SQL, so we lowercase the names here and in the test.
175
- all_cols_tt = [name .lower () for name in all_cols_tt ]
176
+ for ecol in ecolumns :
177
+ if ecol not in all_cols_ot :
178
+ gs .warning (
179
+ _ ("Column <{column}> not found in table <{table}>" ).format (
180
+ column = ecol , table = otable
181
+ )
182
+ )
183
+ else :
184
+ cols_to_update .pop (ecol )
176
185
177
- cols_to_add_final = []
178
- for col in cols_to_add :
179
- # skip the vector column which is used for join
180
- colname = col [0 ]
181
- if colname == column :
182
- continue
186
+ cols_to_add = []
187
+ for col_name , col_desc in cols_to_update .items ():
183
188
use_len = False
184
- if len (col ) > 2 :
189
+ col_type = f"{ col_desc [0 ]} "
190
+ # Sqlite 3 does not support the precision number any more
191
+ if len (col_desc ) > 2 and driver != "sqlite" :
185
192
use_len = True
186
- # Sqlite 3 does not support the precision number any more
187
- if driver == "sqlite" :
188
- use_len = False
189
193
# MySQL - expect format DOUBLE PRECISION(M,D), see #2792
190
- elif driver == "mysql" and col [1 ] == "DOUBLE PRECISION" :
194
+ if driver == "mysql" and col_desc [1 ] == "DOUBLE PRECISION" :
191
195
use_len = False
192
196
193
197
if use_len :
194
- coltype = "%s(%s)" % (col [1 ], col [2 ])
195
- else :
196
- coltype = "%s" % col [1 ]
198
+ col_type = f"{ col_desc [0 ]} ({ col_desc [1 ]} )"
197
199
198
- colspec = "%s %s" % ( colname , coltype )
200
+ col_spec = f" { col_name . lower () } { col_type } "
199
201
200
202
# add only the new column to the table
201
- if colname .lower () not in all_cols_tt :
202
- cols_to_add_final .append (colspec )
203
-
204
- cols_added = [col .split (" " )[0 ] for col in cols_to_add_final ]
205
- cols_added_str = "," .join (cols_added )
206
- try :
207
- grass .run_command (
208
- "v.db.addcolumn" , map = map , columns = cols_to_add_final , layer = layer
209
- )
210
- except CalledModuleError :
211
- grass .fatal (_ ("Error creating columns <{}>" ).format (cols_added_str ))
203
+ if col_name .lower () not in all_cols_tt :
204
+ cols_to_add .append (col_spec )
205
+
206
+ if cols_to_add :
207
+ try :
208
+ gs .run_command (
209
+ "v.db.addcolumn" ,
210
+ map = vector_map ,
211
+ columns = "," .join (cols_to_add ),
212
+ layer = layer ,
213
+ )
214
+ except CalledModuleError :
215
+ gs .fatal (
216
+ _ ("Error creating columns <{}>" ).format (
217
+ ", " .join ([col .split (" " )[0 ] for col in cols_to_add ])
218
+ )
219
+ )
212
220
213
221
update_str = "BEGIN TRANSACTION\n "
214
- for col in cols_added :
222
+ for col in cols_to_update :
215
223
cur_up_str = (
216
224
f"UPDATE { maptable } SET { col } = (SELECT { col } FROM "
217
225
f"{ otable } WHERE "
218
226
f"{ otable } .{ ocolumn } ={ maptable } .{ column } );\n "
219
227
)
220
228
update_str += cur_up_str
221
229
update_str += "END TRANSACTION"
222
- grass .debug (update_str , 1 )
223
- grass .verbose (
230
+ gs .debug (update_str , 1 )
231
+ gs .verbose (
224
232
_ ("Updating columns {columns} of vector map {map_name}..." ).format (
225
- columns = cols_added_str , map_name = map
233
+ columns = ", " . join ( cols_to_update . keys ()), map_name = vector_map
226
234
)
227
235
)
228
- sql_file = grass .tempfile ()
236
+ sql_file = Path ( gs .tempfile () )
229
237
rm_files .append (sql_file )
230
- with open ( sql_file , "w" ) as write_file :
231
- write_file . write ( update_str )
238
+ sql_file . write_text ( update_str , encoding = "UTF8" )
239
+
232
240
try :
233
- grass .run_command (
241
+ gs .run_command (
234
242
"db.execute" ,
235
- input = sql_file ,
243
+ input = str ( sql_file ) ,
236
244
database = database ,
237
245
driver = driver ,
238
246
)
239
247
except CalledModuleError :
240
- grass .fatal (_ ("Error filling columns {}" ).format (cols_added_str ))
248
+ gs .fatal (_ ("Error filling columns {}" ).format (cols_to_update ))
241
249
242
250
# write cmd history
243
- grass .vector_history (map )
251
+ gs .vector_history (vector_map )
244
252
245
253
return 0
246
254
247
255
248
256
if __name__ == "__main__" :
249
- options , flags = grass .parser ()
257
+ options , flags = gs .parser ()
250
258
atexit .register (cleanup )
251
259
sys .exit (main ())
0 commit comments